From 7bdce37fd3f18e2712e18c4e2c64cac69af0aca1 Mon Sep 17 00:00:00 2001 From: Jakob Stendahl Date: Sun, 19 Sep 2021 19:43:11 +0200 Subject: :boom: Introduce new UI based on svelte, and rewrite a lot of the node app and the NeoRuntime --- src/Logger/index.js | 79 +++++ src/NeoRuntimeManager/RuntimeProcess.js | 150 +++++++++ src/NeoRuntimeManager/index.js | 309 ++++++++++++++++++ src/SSLCert/index.js | 144 +++++++++ src/SelfUpdater/index.js | 68 ++++ src/SocketIO/index.js | 353 +++++++++++++++++++++ src/UserData/index.js | 330 +++++++++++++++++++ src/compileAndRun/index.js | 75 ----- src/compileAndRun/process.js | 41 --- .../pythonSupportFiles/LuxcenaNeo/Matrix.py | 73 ----- .../pythonSupportFiles/LuxcenaNeo/NeoBehaviour.py | 29 -- .../pythonSupportFiles/LuxcenaNeo/Strip.py | 121 ------- .../pythonSupportFiles/LuxcenaNeo/__init__.py | 10 - src/compileAndRun/pythonSupportFiles/entry.py | 76 ----- src/domain/middleware.js | 40 --- src/neoRuntime/index.js | 232 -------------- src/public/app.js | 19 -- src/public/app.scss | 9 - src/public/components/sidenav.js | 24 -- src/public/js/general.js | 10 - src/public/js/index.js | 68 ---- src/public/js/logviewer.js | 64 ---- src/public/js/neo_ide.js | 4 - src/public/js/scripts.js | 71 ----- src/public/scss/general.scss | 44 --- src/public/scss/neo_ide.scss | 254 --------------- src/public/scss/scripts.scss | 35 -- src/public/scss/setup.scss | 0 src/public/scss/update.scss | 62 ---- src/runtimeData/index.js | 45 --- src/versionChecker/index.js | 58 ---- 31 files changed, 1433 insertions(+), 1464 deletions(-) create mode 100644 src/Logger/index.js create mode 100644 src/NeoRuntimeManager/RuntimeProcess.js create mode 100644 src/NeoRuntimeManager/index.js create mode 100644 src/SSLCert/index.js create mode 100644 src/SelfUpdater/index.js create mode 100644 src/SocketIO/index.js create mode 100644 src/UserData/index.js delete mode 100644 src/compileAndRun/index.js delete mode 100644 src/compileAndRun/process.js delete mode 100644 src/compileAndRun/pythonSupportFiles/LuxcenaNeo/Matrix.py delete mode 100644 src/compileAndRun/pythonSupportFiles/LuxcenaNeo/NeoBehaviour.py delete mode 100644 src/compileAndRun/pythonSupportFiles/LuxcenaNeo/Strip.py delete mode 100644 src/compileAndRun/pythonSupportFiles/LuxcenaNeo/__init__.py delete mode 100644 src/compileAndRun/pythonSupportFiles/entry.py delete mode 100644 src/domain/middleware.js delete mode 100644 src/neoRuntime/index.js delete mode 100644 src/public/app.js delete mode 100644 src/public/app.scss delete mode 100644 src/public/components/sidenav.js delete mode 100644 src/public/js/general.js delete mode 100644 src/public/js/index.js delete mode 100644 src/public/js/logviewer.js delete mode 100644 src/public/js/neo_ide.js delete mode 100644 src/public/js/scripts.js delete mode 100644 src/public/scss/general.scss delete mode 100644 src/public/scss/neo_ide.scss delete mode 100644 src/public/scss/scripts.scss delete mode 100644 src/public/scss/setup.scss delete mode 100644 src/public/scss/update.scss delete mode 100644 src/runtimeData/index.js delete mode 100644 src/versionChecker/index.js (limited to 'src') diff --git a/src/Logger/index.js b/src/Logger/index.js new file mode 100644 index 0000000..2ee216a --- /dev/null +++ b/src/Logger/index.js @@ -0,0 +1,79 @@ +let fse = require("fs-extra"); + +const level = { + EMERG: "EMERGENCY", + ALERT: "ALERT", + CRIT: "CRITICAL", + ERROR: "ERROR", + WARNING: "WARNING", + NOTICE: "NOTICE", + INFO: "INFO", + DEBUG: "DEBUG", + + ACCESS: "" +}; + + +Object.defineProperty(String.prototype, "lPad", { + value: function lPad(len, chr="0") { + str = this; + var i = -1; + if (!chr && chr !== 0) chr = ' '; + len = len - this.length; + while (++i < len) { + str = chr + str; + } + return str; + }, + writeable: true, + configurable: true +}); + + +function getTimeStamp() { + let CDate = new Date(); + let day = CDate.getDate().toString().lPad(2); + let month = (CDate.getMonth() + 1).toString().lPad(2); // +1 because js starts to count at 0 + let year = CDate.getFullYear(); + let hour = CDate.getHours().toString().lPad(2); + let min = CDate.getMinutes().toString().lPad(2); + let sec = CDate.getSeconds().toString().lPad(2); + let ms = Math.round(CDate.getMilliseconds() / 10).toString().lPad(2); // divide by 10 to make the last digit decimal, then round. + + return `${day}.${month}.${year}-${hour}:${min}:${sec}.${ms}`; +} + + +function log(object, logLevel=level.DEBUG, file="/lux-neo.log") { + fse.ensureFileSync(__logdir + file); + + let formattedLogString = `[${getTimeStamp()}] ${logLevel} ${object}`; + console.log(formattedLogString); // @TODO: This should probably be removed, used for dev currently + + + fse.appendFile( + __logdir + "/lux-neo.log", + formattedLogString + '\n' + ).catch(err => { + console.log("EMERGENCY Could not write to log-file 'lux-neo.log'..."); + console.log("DEBUG FileWriteError: " + err) + }); + + if (__event != undefined) { + __event.emit("logger", logLevel, object); + } +} + +module.exports = { + level, + log, + emerg: (object) => { log(object, level.EMERG); }, + alert: (object) => { log(object, level.ALERT); }, + crit: (object) => { log(object, level.CRIT); }, + error: (object) => { log(object, level.ERROR); }, + warning: (object) => { log(object, level.WARNING); }, + notice: (object) => { log(object, level.NOTICE); }, + info: (object) => { log(object, level.INFO); }, + debug: (object) => { log(object, level.DEBUG); }, + access: (object) => { log(object, level.ACCESS, file="/access.log"); } +}; diff --git a/src/NeoRuntimeManager/RuntimeProcess.js b/src/NeoRuntimeManager/RuntimeProcess.js new file mode 100644 index 0000000..60f6a28 --- /dev/null +++ b/src/NeoRuntimeManager/RuntimeProcess.js @@ -0,0 +1,150 @@ +let fs = require("fs-extra"); +let spawn = require("child_process"); + +class RuntimeProcess { + + constructor(_modePath, _onVarChange, _eventEmitter) { + this.modePath = _modePath; + this.logfile = `${this.modePath}/mode.log`; + + this.stdout = ""; + this.stderr = ""; + + this.fl = false; + this.proc = null; + + this.isRunning = false; + this.exitCode = null; + + this.variables = {}; + this.globvars = {}; + this.onVarChange = _onVarChange; + this.eventEmitter = _eventEmitter; + } + + start() { + if (this.isRunning) { + console.log("PROCESS ALREADY RUNNING"); + return; + } + this.isRunning = true; + this.proc = spawn.spawn( + "python3", + [ + "-u", // This makes us able to get real-time output + `${__basedir}/NeoRuntime/Runtime/neo_runtime.py`, + `--strip-config="${__datadir}/config/strip.ini"`, + `--mode-path="${this.modePath}"`, + `--mode-entry=script` + ] + ); + + this.proc.on('error', (err) => { + console.log(err); + }); + + fs.ensureFileSync(this.logfile); + this.eventEmitter.emit("proc:start"); + + this.proc.stdout.on('data', (_stdout) => { + let stdout_str = _stdout.toString(); + + let regex = /{ ":::data:": { (.*) } }/gi; + let data = stdout_str.match(regex); + stdout_str = stdout_str.replace(regex, () => ""); + + if ((data != null) && (data.length > 0)) { + try { + this.processVarData(data) + } catch {} + } + + if (stdout_str.replace("\n", "").replace(" ", "") == "") { + // In this case, we want to ignore the data. + } else { + // stdout_str = stdout_str.replace(/\n$/, "") + fs.appendFile(this.logfile, "\n====stdout====\n" + stdout_str); + this.eventEmitter.emit("proc:stdout", stdout_str); + } + }); + + this.proc.stdout.on('end', () => { + fs.appendFile(this.logfile, "\n"); + }); + + this.proc.stderr.on('data', (_stderr) => { + // let stderr_str = _stderr.toString().replace(/\n$/, "") + let stderr_str = _stderr.toString() + fs.appendFile(this.logfile, "\n====stderr====\n" + stderr_str); + this.eventEmitter.emit("proc:stderr", stderr_str); + }); + + this.proc.stderr.on('end', () => { + fs.appendFile(this.logfile, "\n"); + }); + + this.proc.on('close', (code) => { + if (code) { + fs.appendFile(this.logfile, "\n====close====\nScript exited with code " + code.toString()); + } + this.eventEmitter.emit("proc:exit", 0); + this.isRunning = false; + this.exitCode = code; + }); + + } + + stop(restart=false) { + try { + if (restart) { + this.proc.once("close", () => { + setTimeout(() => this.start(), 500); + }); + } + this.proc.kill("SIGINT"); + } catch (err) { + console.log(err); + } + } + + processVarData(data) { + data = JSON.parse(data)[":::data:"]; + if (data.hasOwnProperty("globvars")) { + forEachDiff(data["globvars"], this.globvars, (key, newVal) => { + this.onVarChange("globvars", key, newVal); + }); + this.globvars = data["globvars"]; + } + if (data.hasOwnProperty("variables")) { + forEachDiff(data["variables"], this.variables, (key, newVal) => { + this.onVarChange("variables", key, newVal); + }); + this.variables = data["variables"]; + } + } + +} + +const isObject = v => v && typeof v === 'object'; + +/** + * Will call callback on all the differences between the dicts + */ +function forEachDiff(dict1, dict2, callback) { + for (const key of new Set([...Object.keys(dict1), ...Object.keys(dict2)])) { + if (isObject(dict1[key]) && isObject(dict2[key])) { + if (dict1[key].value !== dict2[key].value) { + callback(key, dict1[key]); + } + } else if (dict1[key] !== dict2[key]) { + if (isObject(dict2[key]) && (dict1[key] == null)) { + dict2[key].value = null; + callback(key, dict2[key]) + } else { + callback(key, dict1[key]); + } + } + } +} + +module.exports = RuntimeProcess; diff --git a/src/NeoRuntimeManager/index.js b/src/NeoRuntimeManager/index.js new file mode 100644 index 0000000..62acb8a --- /dev/null +++ b/src/NeoRuntimeManager/index.js @@ -0,0 +1,309 @@ +/** + * This module is used to execute and communicate with a python NeoRuntime instance. + * + * @author jakobst1n. + * @since 19.12.2019 + */ + +const fs = require("fs"); +const fsPromises = fs.promises; +const RuntimeProcess = require("./RuntimeProcess"); +let logger = require(__basedir + "/src/logger"); +const EventEmitter = require('events'); + +/** @type {object} this should be a pointer to a object referencing all neoModules (see app.js) */ +let neoModules; + +/** @type {string} Currently active mode */ +let modeId = null; +/** @type {int} Last exit code of a mode */ +let modeExitCode = 0; +/** @type {RuntimeProcess} This is the current RuntimeProcess instance */ +let runtimeProcess = null; +/** @type {EventEmitter} This is used to emit events when things change */ +const eventEmitter = new EventEmitter(); +/** @type {boolean} If this is true, we will not do things the usual way */ +let modeDebuggerActive = false; +/** @type {string} Should be the modeId the debugger is attached to */ +let modeDebuggerId = null; + +eventEmitter.on("proc:exit", (code) => modeExitCode = code); + +/** + * Check if a path id actually a mode (if it is a folder with a script.py file) + * + * @param {string} path - Path to check. + * + * @return {boolean} wether the path points to a valid mode. + */ +function isMode(path) { + if (!fs.existsSync(path)) { return false; } + let folderStat = fs.statSync(path); + if (!folderStat.isDirectory()) { return false; } + if (!fs.existsSync(path + "/script.py")) { return false; } + return true; +} + +/** + * Get all ids of modes that can be set. + * + * @returns {array} All modeids + */ +function listModes() { + let modeDirs = [ + ["builtin/", fs.readdirSync(__basedir + "/NeoRuntime/builtin")], + ["remote/", fs.readdirSync(__datadir + "/remoteCode")], + ["user/", fs.readdirSync(__datadir + "/userCode")] + ] + let validModes = []; + for (modeDir of modeDirs) { + for (modeName of modeDir[1]) { + let modeId = `${modeDir[0]}${modeName}`; + if (isMode(getModePath(modeId))) { + validModes.push(modeId); + } + } + } + return validModes; +} + +/** + * Change mode, stop the old one and start the new one. + * + * @param {string} _modeId - Id of the mode to change to. + * + * @return {object} A standardform return object. + */ +function setMode(_modeId) { + if (modeDebuggerActive && (_modeId != modeDebuggerId)) { + return {success: false, reason: "debugger active", detail: "Cannot change mode when debugger is active."} + } + if (!isMode(getModePath(_modeId))) { + console.log(`Invalid mode "${_modeId}".`); + return {success: false, reason: "unknown modeId"}; + } + logger.info(`Changing mode to "${_modeId}".`); + + let globvarsTmp = {}; + let variablesTmp = {}; + if (runtimeProcess != null) { + globvarsTmp = runtimeProcess.globvars; + variablesTmp = runtimeProcess.variables; + } + + stopMode(); + + modeId = _modeId; + neoModules.userData.config.activeMode = modeId; + eventEmitter.emit("change", "mode", modeId); + + runtimeProcess = new RuntimeProcess(getModePath(_modeId), onVariableChange, eventEmitter); + runtimeProcess.globvars = globvarsTmp; + runtimeProcess.variables = variablesTmp; + startMode(); + return {success: true} +}; + +/** + * Get current mode + * + * @return {string} current modeId + */ +function currentMode() { + return modeId; +} + +/** + * Will attempt to stop current mode + * + * @return {object} A standardform return object. + */ +function stopMode(restart=false) { + if (modeRunning()) { + runtimeProcess.stop(restart); + } + return {success: true} +}; + +/** + * Will attempt to start current mode + * + * @return {object} A standardform return object. + */ +function startMode() { + if (runtimeProcess === null) { return {success: false, reason: "no runtimeprocess", detail: "Runtimeprocess not set, did you mean to call setMode?"}; } + runtimeProcess.start(); + return {success: true} +}; + +/** + * Will attempt to restart current mode + * + * @return {object} A standardform return object. + */ +function restartMode() { + return stopMode(true); +}; + +/** + * Checks if mode is running currently + * + * @return {boolean} if mode is running + */ +function modeRunning() { + if (runtimeProcess === null) { return false; } + return runtimeProcess.isRunning; +}; + +/** + * Get the full system path to a mode + * + * @param {string} modeId + * + * @return {string} Full path of mode + */ +function getModePath(modeId) { + let path = modeId.split("/"); + let location = path.splice(0, 1).toString(); + if (location === "user") { path = __datadir + "/userCode/" + path.join("/"); } + if (location === "remote") { path = __datadir + "/remoteCode/" + path.join("/"); } + if (location === "builtin") { path = __basedir + "/NeoRuntime/builtin/" + path.join("/"); } + return path; +} + +/** + * This should be called by RuntimeProcess when a variable changes in the mode + * + * @param {string} location - This is globvars/variables + * @param {string} name - Name of the variable + * @param {any} newValue - The new value of the variable + */ +function onVariableChange(location, name, newValue) { + if (location == "variables") { + eventEmitter.emit("change", `variable/${name}`, newValue) + } else if (location == "globvars") { + eventEmitter.emit("change", `${name}`, newValue) + } +} + +/** + * Function that returns all globvars (brightness, power_on) as the values they + * had last time we heard from the python script. + * + * @return {object} + */ +function getGlobvars() { + if (!modeRunning()) { return {}; } + return runtimeProcess.globvars; +} + +/** + * Sets value of a globvar power_on/brightness. + * + * @param {string} name - Name of the variable power_on/brightness + * @param {any} value - The value the variable should be set to + * + * @return {object} Standardform return object + */ +function setGlobvar(name, value) { + if (!modeRunning()) { return; } + runtimeProcess.proc.stdin.write(`:::setglob: ${name}:${value}\n`); + return {success: true} +} + +/** + * Get all variables declared in mode + * + * @return {object} + */ +function getVariables() { + if (!modeRunning()) { return {}; } + return runtimeProcess.variables; +} + +/** + * Sets value of a variable + * + * @param {string} name - Name of the variable + * @param {any} value - The value the variable should be set to + * + * @return {object} Standardform return object + */ +function setVariable(name, value) { + if (!modeRunning()) { return; } + runtimeProcess.proc.stdin.write(`:::setvar: ${name}:${value}\n`); + return {success: true} +} + +/** + * Start debugger for a mode + * + * @param {string} modeId - The mode to debug + * + * @return {object} Standardform return object + */ +function startDebugger(debuggerModeId) { + if (debuggerModeId.substr(0, 5) !== "user/") { return {success: false, reason: "not user mode"}; } + if (!isMode(getModePath(debuggerModeId))) { return {success: false, reason: "unknown modeId"}; } + if (modeDebuggerActive) { return {success: false, reason: "debugger already active"}; } + logger.info(`Starting debugger for ${debuggerModeId}`); + modeDebuggerActive = true; + modeDebuggerId = debuggerModeId; + if (debuggerModeId != modeId) { + setMode(debuggerModeId); + } else { + restartMode(); + } + return {success: true, code: fs.readFileSync(getModePath(debuggerModeId) + "/script.py").toString()} +} + +/** + * Save mode + */ +function saveModeCode(_modeId, code) { + if (!modeDebuggerActive) { return {success: false, reason: "debugger not active"}; }; + if (_modeId != modeDebuggerId) { return {success: false, reason: "modeid not the same as debuggermodeid"}; }; + fs.writeFileSync(getModePath(`${modeDebuggerId}/script.py`), code); + return {success: true}; +} + +/** + * Stop the active debugger + * + * @return {object} Standardform return object + */ +function stopDebugger() { + if (!modeDebuggerActive) { return {success: true, detail: "No debugger active"} } + logger.info(`Stopping debugger`); + modeDebuggerActive = false; + return {success: true} +} + +module.exports = (_neoModules) => { + neoModules = _neoModules; + return { + event: eventEmitter, + modes: listModes, + mode: { + current: currentMode, + set: (modeId) => setMode(modeId), + status: { + modeRunning: modeRunning(), + modeExitCode: modeExitCode + }, + globvars: { + get: getGlobvars, + set: setGlobvar + }, + variables: { + get: getVariables, + set: setVariable + } + }, + getModePath, + isMode, + modeRunning, + startDebugger, stopDebugger, saveModeCode, + startMode, stopMode, restartMode + } +}; \ No newline at end of file diff --git a/src/SSLCert/index.js b/src/SSLCert/index.js new file mode 100644 index 0000000..6dad579 --- /dev/null +++ b/src/SSLCert/index.js @@ -0,0 +1,144 @@ +/** + * Module that exports an instance of CertMon + * see class definition to see what it does. + * + * Requires global var '__datadir' to be set. + * + * @author jakobst1n. + * @since 14.16.2019 + */ + let logger = require(__basedir + "/src/logger"); + const fs = require("fs"); + const { execSync } = require("child_process"); + +var neoModules; + + /** + * This checks if the server has a valid certificate, if not, + * it will generate one. + */ + class CertMon { + + constructor(configPath, certPath, httpsConfig) { + this.certPath = __datadir + "/config/certs/"; + + let valid = this.checkValidity(); + if (!valid) { + logger.notice("No valid certificate found, creating one now."); + this.generateCert(); + } + + let interval = setInterval(() => { + let certIsValid = this.checkValidity(); + if (!valid) { + logger.crit("Certificate no longer valid, server should reboot to make a new one."); + } + }, 1440000); // Run once every day + } + + checkValidity() { + let sslConfig = this.getConfig(); + if (!sslConfig["certMade"]) { + logger.debug("'certMade' in config is false, assuming no valid certificate"); + return false; + } + let expire = ((sslConfig["certExpire"] - Date.now()) / 86400000).toFixed(2); + if (expire > 0) { + logger.debug(`Certificate should be valid for ${expire} more days.`); + } else { + expire = Math.abs(expire); + logger.debug(`Certificate expired ${expire} days ago`); + return false; + } + return true; + } + + getConfig() { + return neoModules.userData.config.SSLCert; + } + + updateConfig(parameters) { + neoModules.userData.config.set(parameters); + } + + generateCert() { + let certPath = this.certPath; + let config = this.getConfig(); + + + // Create Root Certificate Autority + let res = openssl( + `genrsa ` + + `-out "${certPath}/root-CA.key.pem" ` + + `2048` + ); + + // Self sign the Root Certificate Autority + res = openssl( + `req ` + + `-x509 ` + + `-new ` + + `-nodes ` + + `-key "${certPath}/root-CA.key.pem" ` + + `-days 1024 ` + + `-out "${certPath}/root-CA.crt.pem" ` + + `-subj "/C=NO/ST=Oslo/L=Oslo/O=Luxcena Neo Self-Signing Authority/CN=${config.CN}"` + ); + + // Create a Device Certificate for each domain, + // such as example.com, *.example.com, awesome.example.com + // NOTE: You MUST match CN to the domain name or ip address you want to use + res = openssl( + `genrsa ` + + `-out "${certPath}/privkey.pem" ` + + `2048` + ); + + // Create a request from your Device, which your Root CA will sign + res = openssl( + `req ` + + `-new ` + + `-key "${certPath}/privkey.pem" ` + + `-out "${certPath}/csr.pem" ` + + `-subj "/C=NO/ST=Oslo/L=Oslo/O=Luxcena Neo Self-Signing Autohity/CN=${config.CN}"` + ); + + // Sign the request from Device with your Root CA + // -CAserial certs/ca/my-root-ca.srl + res = openssl( + `x509 ` + + `-req ` + + `-in "${certPath}/csr.pem" ` + + `-CA "${certPath}/root-CA.crt.pem" ` + + `-CAkey "${certPath}/root-CA.key.pem" ` + + `-CAcreateserial ` + + `-out "${certPath}/cert.pem" ` + + `-days 500` + ); + + let creationDate = Date.now(); + config.certMade = true; + config.certDate = creationDate; + config.certExpire = creationDate + (500*86400000); + config.certCN = config.CN; + + logger.info("Self-signed certificate created."); + + } + + } + +function openssl(command) { + try { + let stdout = execSync("openssl " + command); + return true + } catch (e) { + return false + } + } + +module.exports = (_neoModules) => { + neoModules = _neoModules; + return new CertMon(); +}; + \ No newline at end of file diff --git a/src/SelfUpdater/index.js b/src/SelfUpdater/index.js new file mode 100644 index 0000000..3c5546b --- /dev/null +++ b/src/SelfUpdater/index.js @@ -0,0 +1,68 @@ +let fs = require("fs-extra"); +let url = require("url"); +let request = require('request'); +let exec = require("child_process").exec; +let logger = require(__basedir + "/src/logger"); + +let neoModules; + +class VersionChecker { + + constructor() { + this.CPackageJson = JSON.parse(fs.readFileSync(__basedir + "/package.json")); + this.version = this.CPackageJson["version"]; + this.repoLink = this.CPackageJson["repository"]["url"]; + + this.checkFrequency = neoModules.userData.config.SelfUpdater.checkVersionInterval * 86400000; // Takes in days. + this.repoBranch = neoModules.userData.config.SelfUpdater.branch; + + this.remotePackageJSON = "https://raw.githubusercontent.com" + url.parse(this.repoLink).pathname + "/" + this.repoBranch + "/package.json"; + + this.newVersion = false; + this.newestVersion = this.checkVersion(this.remotePackageJSON); + + this.updateChecker = setInterval(() => { + let newVersion = this.checkVersion(this.remotePackageJSON); + }, this.checkFrequency); + + } + + checkVersion() { + request.get(this.remotePackageJSON, (error, response, body) => { + if (!error && response.statusCode === 200) { + let remotePackageJSON = JSON.parse(body); + let newestVersion = remotePackageJSON["version"]; + if (this.VersionIsNewerThan(newestVersion, this.version)) { + logger.notice("A new version is available on \"" + this.repoBranch + "\" (v" + this.version + ")"); + this.newVersion = true; + } else { + logger.info(`Running newest version (${newestVersion})`); + this.newVersion = false; + } + this.newestVersion = newestVersion; + } else { + logger.notice("Could not find latest version! Please check you internet connection."); + } + }); + } + + VersionIsNewerThan(check, current) { + let checkParts = check.split("."); + let currentParts = current.split("."); + let lCheckParts = checkParts.length; + let lCurrentParts = currentParts.length; + + for (let i = 0; i < lCheckParts; i++) { + if (i >= lCurrentParts) { return true; } + if (Number (checkParts[i]) > Number (currentParts[i])) { return true; } + if (Number (checkParts[i]) < Number (currentParts[i])) { return false; } + } + return false; + } + +} +  +module.exports = (_neoModules) => { + neoModules = _neoModules; + return new VersionChecker(); +}; diff --git a/src/SocketIO/index.js b/src/SocketIO/index.js new file mode 100644 index 0000000..fdaad56 --- /dev/null +++ b/src/SocketIO/index.js @@ -0,0 +1,353 @@ +/** + * This module contains code for handling socketIO clients. + * + * There are two classes, one is a SocketIO controller module. + * The other one is a authorizedclient. + * + * @author jakobst1n. + * @since 19.12.2019 + */ + +let logger = require(__basedir + "/src/logger"); +var exec = require('child_process').exec; +var CryptoJS = require("crypto-js"); +let fs = require("fs"); + +let neoModules; + +const sanitizePath = (path) => path.match(/(user|remote|builtin\/[a-zA-Z0-9-_\/]{1,200})(\.[a-zA-Z0-9]{1,10})?/)[0]; + +/** + * Create the open socketio namespace and setup all listeners. + * + * @param {io} socketio + */ +function createOpenSocketNamespace(io) { + const openNamespace = io.of("/open") + + openNamespace.on("connection", (socket) => { + logger.access(`SOCKET:open Client (${socket.id}@${socket.handshake.headers.host}) connected.`); + + socket.on("name:get", () => { + socket.emit("name", neoModules.userData.config.instanceName); + }); + socket.on("mode:set", (modeId) => { + neoModules.neoRuntimeManager.mode.set(modeId); + }); + socket.on("mode:get", () => { + socket.emit("mode", neoModules.neoRuntimeManager.mode.current()); + }); + socket.on("modelist:get", () => { + socket.emit("modelist", neoModules.neoRuntimeManager.modes()) + }); + socket.on("brightness:set", (brightness) => { + neoModules.neoRuntimeManager.mode.globvars.set("brightness", brightness); + }); + socket.on("brightness:get", () => { + socket.emit("brightness", neoModules.neoRuntimeManager.mode.globvars.get().brightness); + }); + socket.on("power:set", (power) => { + neoModules.neoRuntimeManager.mode.globvars.set("power_on", power); + }); + socket.on("power:get", () => { + socket.emit("power", neoModules.neoRuntimeManager.mode.globvars.get().power_on); + }); + socket.on("var:set", (name, value) => { + neoModules.neoRuntimeManager.mode.variables.set(name, value); + }); + socket.on("vars:get", () => { + socket.emit("vars", neoModules.neoRuntimeManager.mode.variables.get()); + }); + socket.on("modeinfo:get", () => { + socket.emit("modeinfo", { + mode: neoModules.neoRuntimeManager.mode.current(), + brightness: neoModules.neoRuntimeManager.mode.globvars.get().brightness, + power: neoModules.neoRuntimeManager.mode.globvars.get().power_on, + vars: neoModules.neoRuntimeManager.mode.variables.get() + }); + }); + socket.on("authenticate:user", (username, password, callback) => { + let user = neoModules.userData.user.get(username); + if (user == null) { + callback({success: false, reason: "Invalid username/password"}) + logger.access(`SOCKET:open Client (${socket.id}@${socket.handshake.headers.host}) tried to log in as '${username}', wrong username and/or password.`); + return; + } + + let providedHash = hashPassword(password, user.salt); + if (providedHash.hash == user.password) { + let token = createToken(socket); + while (session_tokens.hasOwnProperty(token)) { + token = createToken(socket); + } + + session_tokens[token] = { + expire: (~~(Date.now()) / 1000)+(86400), + host: socket.handshake.headers.host, + user: {username: user.username} + }; + + callback({success: true, user: {username: username}, token: token}) + logger.access(`SOCKET:open Client (${socket.id}@${socket.handshake.headers.host}) authenticated as user '${username}'`); + return; + } + + callback({success: false, reason: "Invalid username/password"}) + logger.access(`SOCKET:open Client (${socket.id}@${socket.handshake.headers.host}) tried to log in as '${username}', wrong username and/or password.`); + }); + + socket.on("disconnect", () => { + logger.access(`SOCKET:open Client (${socket.id}@${socket.handshake.headers.host}) disconnected.`); + }); + }); + + neoModules.neoRuntimeManager.event.on("change", (name, value) => { + if (name == "modelist") { + openNamespace.emit("modelist", neoModules.neoRuntimeManager.modes()); + } else if (["mode", "power_on", "brightness"].includes(name)) { + if (name == "power_on") { name = "power"; } + openNamespace.emit(name, value); + } else { + openNamespace.emit("var", name, value); + } + }); +} + +/** + * @type {object} This is the collection of valid session tokens. + */ +let session_tokens = {}; + +/** + * Middleware that will stop the request if the client does not have a valid + * session token. + * + * @param {object} socket - The socket instance of the connected client + * @param {function} next - The callback to continue the middleware chain + */ +function authorize_middleware(socket, next) { + const token = socket.handshake.auth.token; + + if (session_tokens.hasOwnProperty(token) && + session_tokens[token].host === socket.handshake.headers.host && + session_tokens[token].expire > (~~(Date.now()))) { + socket.data.user = session_tokens[token].user; + next(); + } else { + const err = new Error("not authorized"); + err.data = { content: "invalid session token" }; + next(err); + } +} + +/** + * Create the open socketio namespace and setup all listeners. + * A valid session token is required to connect to this namespace. + * + * @param {io} socetio + */ +function createAuthorizedNamespace(io) { + const authorizedNamespace = io.of("/authed"); + authorizedNamespace.use(authorize_middleware); + authorizedNamespace.on("connection", (socket) => { + logger.access(`SOCKET:authed Client (${socket.id}@${socket.handshake.headers.host}) connected.`); + let debuggerOpen = false; + + socket.emit("user", socket.data.user); + + /* InstanceName */ + socket.on("name:set", (name, fn) => { + neoModules.userData.config.instanceName = name; + fn({success: true}); + io.emit("name", neoModules.userData.config.instanceName); + }); + + /* UserData */ + socket.on("mode:create", (name, template, fn) => { + fn(neoModules.userData.mode.create(name, template)); + }); + socket.on("mode:delete", (modeid, fn) => { + fn(neoModules.userData.mode.delete(modeid)); + }); + + /* LED Config */ + socket.on("led_config:get", () => { + socket.emit("led_config", neoModules.userData.strip.get()); + }); + socket.on("led_config:set", (config) => { + neoModules.userData.strip.set(config); + }); + + /* SelfUpdater */ + socket.on("version:current_number", () => { + socket.emit("version:current_number", neoModules.selfUpdater.version); + }); + socket.on("version:branch", (fn) => { + socket.emit("version:branch", neoModules.selfUpdater.repoBranch); + }); + socket.on("version:newest_number", (fn) => { + socket.emit("version:newest_number", neoModules.selfUpdater.newestVersion); + }); + socket.on("version:check_for_update", (fn) => { + neoModules.selfUpdater.checkVersion(); + socket.emit("version:newest_number", neoModules.selfUpdater.newestVersion); + fn({success: true}); + }); + socket.on("system:update_version", () => { + }); + + /* SSLCert */ + socket.on("sslcert:info", (fn) => { + socket.emit("sslcert:info", {...neoModules.SSLCert.getConfig(), "isValid": neoModules.SSLCert.checkValidity()}); + }); + socket.on("sslcert:generate_new", (fn) => { + neoModules.SSLCert.generateCert(); + fn({success: true}); + }); + + /* System actions */ + socket.on("restart:system", () => { + exec('shutdown -r now', function(error, stdout, stderr){ callback(stdout); }); + }); + socket.on("restart:service", () => { + let p = exec('systemctl restart luxcena-neo'); + p.unref(); + }); + + /* Users */ + socket.on("users:get", () => { + socket.emit("users", neoModules.userData.users()) + }); + socket.on("user:delete", (username, fn) => { + if (username == socket.data.user.username) { fn({success: false, reason: "cannot delete logged in account"}); return; } + fn(neoModules.userData.user.delete(username)); + socket.emit("users", neoModules.userData.users()) + }); + socket.on("user:changeusername", (oldusername, newusername, fn) => { + if (oldusername == socket.data.user.username) { fn({success: false, reason: "cannot change username of logged in account"}); return; } + let user = neoModules.userData.user.get(oldUserName); + if (user == null) { fn({success: false, reason: "unknown username", detail: oldusername}); return; } + user.username = newusername; + let res = neoModules.userData.user.save(user); + if (!res.success) { fn(res); return; } + res = neoModules.userData.user.delete(oldusername) + if (!res.success) { fn(res); return; } + fn({success: true}); + socket.emit("users", neoModules.userData.users()) + }); + socket.on("user:newpassword", (username, newPassword, fn) => { + let user = neoModules.userData.user.get(username); + if (user == null) { fn({success: false, reason: "unknown username", detail: username}); return; } + let newHash = hashPassword(newPassword); + fn(neoModules.userData.user.save(username, newHash.salt, newHash.hash)); + socket.emit("users", neoModules.userData.users()) + }); + socket.on("user:create", (username, newPassword, fn) => { + let user = neoModules.userData.user.get(username); + if (user != null) { fn({success: false, reason: "user already exists", detail: username}); return; } + if (username.length < 1) { fn({success: false, reason: "no username provided"}); return; } + let newHash = hashPassword(newPassword); + fn(neoModules.userData.user.save(username, newHash.salt, newHash.hash)); + socket.emit("users", neoModules.userData.users()) + }); + + /* Editor/debugger */ + let onProcStart = () => socket.emit("editor:proc:start"); + let onProcStop = (code) => socket.emit("editor:proc:exit", code); + let onProcStdout = (stdout) => socket.emit("editor:proc:stdout", stdout); + let onProcStderr = (stderr) => socket.emit("editor:proc:stderr", stderr); + let closeDebugger = () => { + debuggerOpen = false; + neoModules.neoRuntimeManager.event.removeListener("proc:start", onProcStart); + neoModules.neoRuntimeManager.event.removeListener("proc:stop", onProcStop); + neoModules.neoRuntimeManager.event.removeListener("proc:stdout", onProcStdout); + neoModules.neoRuntimeManager.event.removeListener("proc:stderr", onProcStderr); + return neoModules.neoRuntimeManager.stopDebugger(); + }; + socket.on("editor:open", (modeId, fn) => { + let res = neoModules.neoRuntimeManager.startDebugger(modeId); + if (!res.success) { fn(res); return; } + logger.info(`Starting debugger for ${modeId}.`) + debuggerOpen = true; + fn({success: true}) + socket.emit("editor:code", modeId, res.code); + + neoModules.neoRuntimeManager.event.on("proc:start", onProcStart); + neoModules.neoRuntimeManager.event.on("proc:exit", onProcStop); + neoModules.neoRuntimeManager.event.on("proc:stdout", onProcStdout); + neoModules.neoRuntimeManager.event.on("proc:stderr", onProcStderr); + if (neoModules.neoRuntimeManager.modeRunning()) { + socket.emit("editor:proc:start"); + } + }); + socket.on("editor:save", (modeId, code, fn) => { + if (!debuggerOpen) { fn({success: false, reason: "Debugger not open"}); return; }; + fn(neoModules.neoRuntimeManager.saveModeCode(modeId, code)); + }); + socket.on("editor:startmode", (fn) => { + fn(neoModules.neoRuntimeManager.startMode()); + }); + socket.on("editor:stopmode", (fn) => { + fn(neoModules.neoRuntimeManager.stopMode()); + }); + socket.on("editor:restartmode", (fn) => { + fn(neoModules.neoRuntimeManager.restartMode()); + }); + socket.on("editor:close", (fn) => { + fn(closeDebugger()); + logger.info("Stopped debugger"); + }); + + socket.on("disconnect", () => { + logger.access(`SOCKET:authed Client (${socket.id}@${socket.handshake.headers.host}) disconnected.`); + if (debuggerOpen) { + closeDebugger(); + logger.info("Stopped debugger because client disconnected") + } + }); + }); +} + +/** + * Creates an access-token from the clients host-name and the current EPOCH. + * + * @param {client} + * + * @return {string} - The access-token. + */ + function createToken(client) { + let time = Date.now().toString(); + let host = client.handshake.headers.host; + return (CryptoJS.SHA256(`${host}${time}`).toString()); +} + +/** + * Create a new salt and hash from a password. + * + * @param {string} password - The password to hash. + * @param {string} salt - If set, this salt will be used, else a new salt is generated. + * + * @return {object} A object containing a password and a salt property. + */ +function hashPassword(password, salt = null) { + if (salt == null) { + salt = CryptoJS.lib.WordArray.random(128 / 2); + } else { + salt = CryptoJS.enc.Hex.parse(salt); + } + let hash = CryptoJS.PBKDF2(password, salt, { + keySize: 512 / 32, + iterations: 1000, + hasher: CryptoJS.algo.SHA512 + }); + return {hash: hash.toString(), salt: salt.toString()} +} + +module.exports = (_neoModules, io) => { + neoModules = _neoModules; + return { + openNamespace: createOpenSocketNamespace(io), + authorizedNamespace: createAuthorizedNamespace(io) + } +}; + \ No newline at end of file diff --git a/src/UserData/index.js b/src/UserData/index.js new file mode 100644 index 0000000..e5318c9 --- /dev/null +++ b/src/UserData/index.js @@ -0,0 +1,330 @@ +/** + * This module is the entry of UserData. This will ensure the user-dirs and all config-files. + * Also, it will + * + * @author jakobst1n. + * @since 19.12.2019 + */ + +let logger = require(__basedir + "/src/logger"); +let fse = require("fs-extra"); +let ini = require('ini'); + +let neoModules; + +/** + * This method will ensure that all required fields are in config.ini + */ +function ensureMainConfig() { + var config = ini.decode(fse.readFileSync(__datadir + "/config/config.ini", 'utf-8')) + + if (config.instanceName == null) { config.instanceName = "neoStrip"; } + if (config.activeMode == null) { config.activeMode = "builtin/static"; } + + if (config.HTTP == null) { config.HTTP = {}; } + if (config.HTTP.port == null) { config.HTTP.port = 443; } + + if (config.SelfUpdater == null) { config.SelfUpdater = {}; } + if (config.SelfUpdater.branch == null) { config.SelfUpdater.branch = "master"; } + if (config.SelfUpdater.checkVersionInterval == null) { config.SelfUpdater.checkVersionInterval = 1; } + if (config.SelfUpdater.automaticUpdate == null) { config.SelfUpdater.automaticUpdate = false; } + + if (config.SSLCert == null) { config.SSLCert = {}; } + if (config.SSLCert.CN == null) { config.SSLCert.CN = "localhost"; } + if (config.SSLCert.certMade == null) { config.SSLCert.certMade = false; } + if (config.SSLCert.certDate == null) { config.SSLCert.certDate = 0; } + if (config.SSLCert.certExpire == null) { config.SSLCert.certExpire = 0; } + if (config.SSLCert.certCN == null) { config.SSLCert.certCN = ""; } + + if (config.DiscoveryServer == null) { config.DiscoveryServer = {}; } + if (config.DiscoveryServer.address == null) { config.DiscoveryServer.address = "https://erj46s.deta.dev"; } + if (config.DiscoveryServer.broadcastSelf == null) { config.DiscoveryServer.broadcastSelf = false; } + + fse.writeFileSync(__datadir + "/config/config.ini", ini.encode(config)) +} + +/** + * This method will ensure that all required fields are in config.ini + */ +function ensureStripConfig() { + var config = ini.decode(fse.readFileSync(__datadir + "/config/strip.ini", 'utf-8')) + + if (config.DEFAULT == null) { config.DEFAULT = {}; } + if (config.DEFAULT.led_pin == null) { config.DEFAULT.led_pin = 18; } + if (config.DEFAULT.led_freq_hz == null) { config.DEFAULT.led_freq_hz = 80000; } + if (config.DEFAULT.led_dma == null) { config.DEFAULT.led_dma = 10; } + if (config.DEFAULT.led_invert == null) { config.DEFAULT.led_invert = false; } + if (config.DEFAULT.led_channel == null) { config.DEFAULT.led_channel = 0 } + if (config.DEFAULT.segments == null) { config.DEFAULT.segments = ""; } + if (config.DEFAULT.matrix == null) { config.DEFAULT.matrix = ""; } + + fse.writeFileSync(__datadir + "/config/strip.ini", ini.encode(config)) +} + +/** + * This method will make sure all files and folders needed for the app exists, + * it will also make sure all files contain all needed data. + */ +function init() { + // Generate all user-folders + logger.info("Ensuring all folder in UserDir exists..."); + + fse.ensureDirSync(__datadir + "/"); + fse.ensureDirSync(__datadir + "/config/"); + fse.ensureDirSync(__datadir + "/config/certs"); + fse.ensureDirSync(__datadir + "/userCode/"); + fse.ensureDirSync(__datadir + "/remoteCode/"); + + // Generate config-files + if (!fse.existsSync(__datadir + "/config/config.ini")) { + fse.closeSync(fse.openSync(__datadir + "/config/config.ini", 'w')); + } + ensureMainConfig(); + + if (!fse.existsSync(__datadir + "/config/strip.ini")) { + fse.closeSync(fse.openSync(__datadir + "/config/strip.ini", 'w')); + } + ensureStripConfig(); + + if (!fse.existsSync(__datadir + "/config/users.ini")) { + fse.writeFileSync(__datadir + "/config/users.ini", ini.encode({ + "neo": { + "password": "5adbc90fb4716fff62d9cf634837e22f29b011803ba29cee51f921b920fa941651737bd15d00dc72e4cbeee5e64e06ec99cc50ea917285a029797a98740cce0f", + "salt": "59b6de1040f3ae3c63de984ca5d61ef46f41dc6ecead3a9d5dab69f0bb3636aa49017e179b74dbcdb407f62bc139a7d55aa78fe2bbdd5327609ea124b2fa03b1" + } + })) + } +}; + +/** + * Recursive function which adds setters and getters to all properties + * in a nested object. This will make us able to save config values + * directly without doing anything other that `prop = value`. + * + * @param {object} config - The full config object. + * @param {string} configFile - The path of the configfile. + * + * @return {object} The config object with setters for values. + */ + function withSetters(config, configFile) { + let outConfig = {}; + function iter(inNode, outNode) { + for (const key of Object.keys(inNode)) { + if (typeof(inNode[key]) === "object") { + outNode[key] = {}; + iter(inNode[key], outNode[key]); + } else { + outNode[`_${key}`] = inNode[key]; + Object.defineProperty(outNode, key, { + get: function() { return this[`_${key}`]; }, + set: function(value) { + this[`_${key}`] = value; + saveConfig(configFile, outConfig); + }, + enumerable: true + }); + } + } + } + iter(config, outConfig); + return outConfig +} + +/** + * Returns a object with only the actual values and not setters, this is the + * inverse of withSetters. + * + * @param {object} config - The full config object. + * + * @return {object} The config object without setters. + */ +function withoutSetters(config) { + let out = {}; + function iter(inNode, outNode) { + for (const key of Object.keys(inNode).filter(k => (k.substr(0, 1) != "_"))) { + if (typeof(inNode[key]) === "object") { + outNode[key] = {}; + iter(inNode[key], outNode[key], out); + } else { + outNode[key] = inNode[`_${key}`]; + } + } + } + iter(config, out); + return out; +} + +/** + * Save config object, this will run stripSetters on the object it saves. + * + * @param {string} file - filename to save the config object to. + * @param {object} object - the config object to save. + */ +function saveConfig(file, object, removeSetters=true) { + if (removeSetters) { + object = withoutSetters(object); + } + fse.writeFileSync(file, ini.encode(object)); +} + +/** + * Reads a ini file and add setters to all properties + * + * @param {string} file - filename of file to read. + * + * @return {object} The config in the file. + */ +function getFullConfig(file, addSetters=true) { + let fullConfig = ini.decode(fse.readFileSync(file, "utf-8")); + if (addSetters) { + fullConfig = withSetters(fullConfig, file); + } + return fullConfig; +} + +/** + * Save a user the user config file, this will append if a new user, and + * overwrite if it is a existsing user. + * + * @param {string} username - The username, case-insensitive. + * @param {string} salt - Salt used for password-checking. + * @param {string} password - Hashed password. + * + * @return {object} Standardform return object + */ + function saveUser(username, salt, password) { + let config = ini.decode(fse.readFileSync(__datadir + "/config/users.ini", 'utf-8')) + config[username] = {} + config[username].salt = salt + config[username].password = password + fse.writeFileSync(__datadir + "/config/users.ini", ini.encode(config)) + return {success: true} +} + +/** + * Get a user, this will return null if no user is found. + * + * @return {object} with username, salt and hash properties. + */ +function getUser(username) { + let config = ini.decode(fse.readFileSync(__datadir + "/config/users.ini", 'utf-8')) + if (Object.prototype.hasOwnProperty.call(config, username)) { + return {...config[username], username: username} + } + return null; +} + +/** + * Get all users + * + * @return {array} usernames + */ +function getUsers() { + let config = ini.decode(fse.readFileSync(__datadir + "/config/users.ini", "utf-8")); + let users = []; + for (const username of Object.keys(config)) { + users.push(username); + } + return users; +} + +/** + * Delete a user + * + * @return {object} Standardform success object. + */ +function deleteUser(username) { + let config = ini.decode(fse.readFileSync(__datadir + "/config/users.ini", 'utf-8')) + if (config.length <= 1) { return {success: false, reason: "cannot delete only user"}; } + if (!Object.prototype.hasOwnProperty.call(config, username)) { return {success: false, reason: "user not found", detail: username}; } + delete config[username]; + fse.writeFileSync(__datadir + "/config/users.ini", ini.encode(config)); + return {success: true} +} + +/** + * Create a new mode in the user directory. + * + * @param {string} name - The name of the file to use, a trailing number will + * be added if there are any conflicts. + * @param {string} template - Id of the template, builtin/static, template/base etc... + * + * @return {object} a standard convention result object. + */ +function createNewUserMode(name, template) { + source_script = null; + if ((template === "template/base") || (template === "") || (template == null)) { + source_script = __basedir + "/NeoRuntime/special/template_base/"; + } else { + source_script = neoModules.neoRuntimeManager.getModePath(template); + } + if (!neoModules.neoRuntimeManager.isMode(source_script)) { + return {success: false, reason: "Source script not found"}; + } + + let newModeName = neoModules.neoRuntimeManager.getModePath(`user/${name}`); + let counter = 0; + while (neoModules.neoRuntimeManager.isMode(newModeName)) { + counter += 1; + newModeName = neoModules.neoRuntimeManager.getModePath(`user/${name}_${counter}`); + } + + fse.ensureDirSync(newModeName); + fse.copySync(`${source_script}/script.py`, `${newModeName}/script.py`) + neoModules.neoRuntimeManager.event.emit("change", "modelist"); + return {success: true}; +} + +/** + * Delete a user created mode + * + * @param {string} modeid - modeid to delete + * + * @return {object} a standard convention result object. + */ +function deleteUserMode(modeid) { + if (modeid.substr(0, 5) !== "user/") { + return {success: false, reason: "Not user mode"} + } + let modePath = neoModules.neoRuntimeManager.getModePath(modeid); + if (!neoModules.neoRuntimeManager.isMode(modePath)) { + return {success: false, reason: "Mode does not found"} + } + if (modeid === neoModules.neoRuntimeManager.mode.current()) { + return {success: false, reason: "Cannot delete currently active mode"} + } + fse.removeSync(modePath); + neoModules.neoRuntimeManager.event.emit("change", "modelist"); + return {success: true} +} + +module.exports = (_neoModules) => { + neoModules = _neoModules; + init(); + return { + users: getUsers, + user: { + save: saveUser, + get: getUser, + delete: deleteUser + }, + strip: { + get: () => { + let c = getFullConfig(`${__datadir}/config/strip.ini`, addSetters=false); + c.DEFAULT.matrix = JSON.parse(c.DEFAULT.matrix); + c.DEFAULT.segments = c.DEFAULT.segments.split(" "); + return c.DEFAULT; + }, + set: (c) => { + c.segments = c.segments.join(" "); + c.matrix = JSON.stringify(c.matrix); + return saveConfig(`${__datadir}/config/strip.ini`, {DEFAULT: c}, removeSetters=false); + }, + }, + config: getFullConfig(`${__datadir}/config/config.ini`), + mode: { + create: createNewUserMode, + delete: deleteUserMode + } + } +}; \ No newline at end of file diff --git a/src/compileAndRun/index.js b/src/compileAndRun/index.js deleted file mode 100644 index 6f89e44..0000000 --- a/src/compileAndRun/index.js +++ /dev/null @@ -1,75 +0,0 @@ -let EventEmitter = require('events'); -let fse = require("fs-extra"); -let Process = require("./process"); -let pythonSupportFiles = __dirname + "/pythonSupportFiles/"; - -class Python extends EventEmitter { - - constructor(profileFolder, usrDataFolder) { - super(); - this.profileFolder = profileFolder; - this.usrDataFolder = usrDataFolder; - - this.stdout = ""; - this.stderr = ""; - } - - status() { - return {}; - } - - compile() { - try { - fse.removeSync(this.profileFolder + "/build"); - fse.copySync(pythonSupportFiles, this.profileFolder + "/build/"); - fse.copySync(this.profileFolder + "/script.py", this.profileFolder + "/build/script.py"); - } catch (err) { - console.log(err); - } - } - - run() { - // Setup paths and commands - let entryPath = this.profileFolder + "/build/entry.py"; - - // Spawn the new process - this.pyProc = new Process("python", [ - "-u", // This makes us able to get real-time output - entryPath, // This is where the entry-point is located - this.usrDataFolder // This is required for the python-script to now where our config-files are - ]); - this.pyProc.start(); - - this.pyProc.proc.stdout.on("data", (_stdout) => { - this.emit("stdout::data", "" + _stdout); - }); - this.pyProc.proc.stdout.on("end", () => { - this.emit("stdout::end"); - }); - this.pyProc.proc.stderr.on("data", (_stderr) => { - this.emit("stderr::data", _stderr); - }); - this.pyProc.proc.stderr.on("end", () => { - this.emit("stderr:end"); - }); - this.pyProc.proc.on("close", (_code) => { - this.emit("close", _code); - }); - - } - - compileAndRun() { - this.compile(); - this.run(); - } - - stop() { - this.pyProc.proc.kill("SIGINT"); - } - -} - -module.exports = { - Python: Python, - Process: Process -}; diff --git a/src/compileAndRun/process.js b/src/compileAndRun/process.js deleted file mode 100644 index 67ff546..0000000 --- a/src/compileAndRun/process.js +++ /dev/null @@ -1,41 +0,0 @@ -let spawn = require("child_process"); - -class Process { - - constructor(command, args = []) { - this.command = command; - this.args = args; - this.stdout = ""; - this.stderr = ""; - this.fl = false; - } - - start() { - this.proc = spawn.spawn(this.command, this.args); - - this.proc.on('error', (err) => { - console.log(err); - }); - - this.proc.stdout.on('data', (_stdout) => { - this.stdout += _stdout; - }); - - this.proc.stdout.on('end', () => { - }); - - this.proc.stderr.on('data', (_stderr) => { - this.stderr += _stderr; - }); - - this.proc.stderr.on('end', () => { - }); - - this.proc.on('close', (code) => { - }); - - } - -} - -module.exports = Process; \ No newline at end of file diff --git a/src/compileAndRun/pythonSupportFiles/LuxcenaNeo/Matrix.py b/src/compileAndRun/pythonSupportFiles/LuxcenaNeo/Matrix.py deleted file mode 100644 index 320da02..0000000 --- a/src/compileAndRun/pythonSupportFiles/LuxcenaNeo/Matrix.py +++ /dev/null @@ -1,73 +0,0 @@ -def getSegmentRange(segments, n): - """ Return a list of all the actual led-numbers in a segment """ - # Sum all the segments prior to the one we are looking for - i = 0 - start = 0 - while True: - if i >= n: break - start += segments[i] # Add number of leds in this segment to the start-index - i += 1 - - # Add all numbers in the segment we are looking for to a list - i = start - breakPoint = i + segments[n] - range = [] - while True: - range.append(i) - i += 1 - if i >= breakPoint: break - return range - - -class Matrix: - - def __init__(self, segments, matrix): - self.matrix = [] # Holds the matrix - self.xLen = 0 # The width of the matrix - self.yLen = len(matrix) # The heigth of the matrix - - for yPos in range(len(matrix)): - yVal = matrix[yPos] - - thisY = [] - for xPos in range(len(yVal)): - # This gets the range of segment n - segmentRange = getSegmentRange(segments, matrix[yPos][xPos][0]) - - # This adds the range to the current row's list - # if in the config [, ] - # reversed == true, revese the list before adding it - thisY += reversed(segmentRange) if matrix[yPos][xPos][1] else segmentRange - - # This just finds the longest row in the matrix - if (len(thisY) > self.xLen): - self.xLen = len(thisY) - - self.matrix.append(thisY) - - def get(self, x, y): - """ Return the value of a place in the matrix given x and y coordinates """ - return self.matrix[y][x] - - def dump(self): - nSpacers = (self.xLen*6) // 2 - 6 - print( ("=" * nSpacers) + "Matrix dump" + ("=" * nSpacers) ) - - for y in self.matrix: - thisYLine = "" - for x in y: - thisYLine += ( ' ' * (5 - len(str(x))) ) + str(x) + ' ' - print(thisYLine) - - print("=" * (self.xLen*6)) - - -if __name__ == "__main__": - testMatrix = Matrix( - [2, 2, 2, 2, 2, 2, 2, 2, 2], - [ - [[0, False], [1, True], [2, False]], - [[3, True], [4, False], [5, True]], - [[6, False], [7, True], [8, False]] - ] - ) diff --git a/src/compileAndRun/pythonSupportFiles/LuxcenaNeo/NeoBehaviour.py b/src/compileAndRun/pythonSupportFiles/LuxcenaNeo/NeoBehaviour.py deleted file mode 100644 index b0238e7..0000000 --- a/src/compileAndRun/pythonSupportFiles/LuxcenaNeo/NeoBehaviour.py +++ /dev/null @@ -1,29 +0,0 @@ -# This is the base-class "main" should inherit from! -# All methods are blocking :) This means that you could potentially loose a "tick" -# For example, if "eachSecond" is taking up the thread, and the clock goes from 11:58 to 12:02, "eachHour", will not be called. -class NeoBehaviour: - - # THIS METHOD SHOULD NOT BE OVERIDDEN! Use onStart if you want a setup-method!!! - # Contains basic setup - def __init__(self): - return - - # This method will be run right after __init__ - def onStart(self): - return - - # This method is called every second (on the clock), given that the thread is open - def eachSecond(self): - return - - # This method is called every mintue (on the clock), given that the thread is open - def eachMinute(self): - return - - # This method is called every whole hour (on the clock), given that the thread is open - def eachHour(self): - return - - # This method is called every day at noon, given that the thread is open - def eachDay(self): - return diff --git a/src/compileAndRun/pythonSupportFiles/LuxcenaNeo/Strip.py b/src/compileAndRun/pythonSupportFiles/LuxcenaNeo/Strip.py deleted file mode 100644 index c3a913f..0000000 --- a/src/compileAndRun/pythonSupportFiles/LuxcenaNeo/Strip.py +++ /dev/null @@ -1,121 +0,0 @@ -from neopixel import * -from Matrix import Matrix, getSegmentRange - -class Strip: - - def __init__(self, stripConf): - self.SEGMENTS = stripConf["segments"] - self.SEGMENT_CONFIG = stripConf["segment_config"] - - self.LED_FREQ_HZ = stripConf["led_freq_hz"] # LED signal frequency in hertz (usually 800khz) - self.LED_CHANNEL = stripConf["led_channel"] # Set to '1' for GPIOs 13, 19, 41, 45, 53 - self.LED_INVERT = stripConf["led_invert"] # True to invert the signal, (when using NPN transistor level shift) - self.LED_PIN = stripConf["led_pin"] # 18 uses PWM, 10 uses SPI /dev/spidev0.0 - self.LED_DMA = stripConf["led_dma"] # DMA channel for generating the signal, on the newer ones, try 10 - self.LED_COUNT = sum(self.SEGMENTS) # Number of LEDs in strip - - - self.LED_BRIGHTNESS = 255 - - self.strip = Adafruit_NeoPixel( - self.LED_COUNT, - self.LED_PIN, - self.LED_FREQ_HZ, - self.LED_DMA, - self.LED_INVERT, - self.LED_BRIGHTNESS, - self.LED_CHANNEL - ) - - self.strip.begin() - - # Blank out all the LEDs - i = 0 - while True: - self.strip.setPixelColor(i, 0) - i += 1 - if (i > self.LED_COUNT): break - self.strip.show() - - # Setup matrix - print(" * Generating matrix") - #try: - self.pixelMatrix = Matrix(self.SEGMENTS, stripConf["matrix"]) - self.pixelMatrix.dump() - #except: - # print("Something went wrong while setting up your self-defined matrix.") - - def show(self): - """Update the display with the data from the LED buffer.""" - self.strip.show() - - def setPixelColor(self, n, color): - """Set LED at position n to the provided 24-bit color value (in RGB order). - """ - self.strip.setPixelColor(n, color) - - def setPixelColorXY(self, x, y, color): - """Set LED at position n to the provided 24-bit color value (in RGB order). - """ - self.strip.setPixelColor(self.pixelMatrix.get(x, y), color) - - def setPixelColorRGB(self, n, red, green, blue, white = 0): - """Set LED at position n to the provided red, green, and blue color. - Each color component should be a value from 0 to 255 (where 0 is the - lowest intensity and 255 is the highest intensity). - """ - self.strip.setPixelColor(n, Color(red, green, blue, white)) - - def setPixelColorXYRGB(self, x, y, red, green, blue, white = 0): - """Set LED at position n to the provided red, green, and blue color. - Each color component should be a value from 0 to 255 (where 0 is the - lowest intensity and 255 is the highest intensity). - """ - self.strip.setPixelColor(self.pixelMatrix.get(x, y), Color(red, green, blue, white)) - - def setSegmentColorRGB(self, segment, red, green, blue, white = 0): - """Set a whole segment to the provided red, green and blue color. - Each color component should be a value from 0 to 255 (where 0 is the - lowest intensity and 255 is the highest intensity).""" - for n in getSegmentRange(self.SEGMENTS, segment): - self.strip.setPixelColor(n, Color(red, green, blue, white)) - - def setBrightness(self, brightness): - """Scale each LED in the buffer by the provided brightness. A brightness - of 0 is the darkest and 255 is the brightest. - """ - self.strip.setBrightness(brightness) - - def getBrightness(self): - """Get the brightness value for each LED in the buffer. A brightness - of 0 is the darkest and 255 is the brightest. - """ - return self.strip.getBrightness() - - def getPixels(self): - """Return an object which allows access to the LED display data as if - it were a sequence of 24-bit RGB values. - """ - return self.strip.getPixels() - - def numPixels(self): - """Return the number of pixels in the display.""" - return self.LED_COUNT - - def getPixelColor(self, n): - """Get the 24-bit RGB color value for the LED at position n.""" - return self.strip.getPixelColor(n) - - -def Color(red, green, blue, white = 0): - """Convert the provided red, green, blue color to a 24-bit color value. - Each color component should be a value 0-255 where 0 is the lowest intensity - and 255 is the highest intensity. - """ - return (white << 24) | (red << 16)| (green << 8) | blue - -def hexColor(value): - value = value.lstrip('#') - lv = len(value) - rgb = tuple(int(value[i:i+lv/3], 16) for i in range(0, lv, lv/3)) - return (0 << 24) | (rgb[1] << 16) | (rgb[0] << 8) | rgb[2] diff --git a/src/compileAndRun/pythonSupportFiles/LuxcenaNeo/__init__.py b/src/compileAndRun/pythonSupportFiles/LuxcenaNeo/__init__.py deleted file mode 100644 index 40b8e2d..0000000 --- a/src/compileAndRun/pythonSupportFiles/LuxcenaNeo/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -from NeoBehaviour import * -from Strip import * - -strip = None # The strip object, should be set in entry.py, and is then accessible in the script-file -forceStop = False # When set to true, execution of the script will halt - -# A function that could be used to halt the script -def stop(): - global forceStop - forceStop = True diff --git a/src/compileAndRun/pythonSupportFiles/entry.py b/src/compileAndRun/pythonSupportFiles/entry.py deleted file mode 100644 index 45de822..0000000 --- a/src/compileAndRun/pythonSupportFiles/entry.py +++ /dev/null @@ -1,76 +0,0 @@ -# This is the entry-point for all Luxcena-Neo python-scripts -# The script should be in the same folder as this, and be named "script.py" -# In the future you could possibly have more files and stuff alongside the "script.py"-file as well -import sys -import json -import importlib -import datetime - -def runSync(moduleSc, sc): - timeNow = datetime.datetime.now() - lastDay = timeNow.day - lastHour = timeNow.hour - lastMinute = timeNow.minute - lastSecond = timeNow.second - - while True: - timeNow = datetime.datetime.now() - - if ("LuxcenaNeo" in dir(moduleSc)): - if moduleSc.LuxcenaNeo.forceStop: break - elif ("neo" in dir(moduleSc)): - if moduleSc.neo.forceStop == True: break - - if (timeNow.second != lastSecond): - lastSecond = timeNow.second - sc.eachSecond() - - if (timeNow.minute != lastMinute): - lastMinute = timeNow.minute - sc.eachMinute() - - if (timeNow.hour != lastHour): - lastHour = timeNow.hour - sc.eachHour() - - if (timeNow.day != lastDay): - lastDay = timeNow.lastDay - sc.eachDay() - -def runAsync(moduleSc, sc): - return - -def main(): - print ("Starting script named \"{0}\"".format("test")) - - root_dir = sys.argv[1] - config_dir = root_dir + "/config/" - - print ("> Loading pixel-configuration...") - with open(config_dir + "strip.json", "r") as rawStripConf: - stripConf = json.load(rawStripConf) - - print ("> Initializing script...") - moduleSc = importlib.import_module("script") - - if ("LuxcenaNeo" in dir(moduleSc)): - moduleSc.LuxcenaNeo.strip = moduleSc.LuxcenaNeo.Strip(stripConf) - elif ("neo" in dir(moduleSc)): - moduleSc.neo.strip = moduleSc.neo.Strip(stripConf) - else: - raise Exception("Neither LuxcenaNeo nor neo found in script, check docs!") - - sc = moduleSc.Main() - - print ("> Running the script...") - sc.onStart() - - if (("async" in dir(moduleSc)) and (moduleSc.async == True)): - runAsync(moduleSc, sc) - else: - runSync(moduleSc, sc) - - print ("> Script exited...") - -if __name__ == "__main__": - main() diff --git a/src/domain/middleware.js b/src/domain/middleware.js deleted file mode 100644 index ebd00f8..0000000 --- a/src/domain/middleware.js +++ /dev/null @@ -1,40 +0,0 @@ -module.exports = function(options) { - return function(req, res, next) { - let dirPublic = options.srcDir + "/public/"; - - if (global.runtimeData.get("updaterRunning") === true) { - res.sendFile(dirPublic + "/update/index.html"); - return; - } - - switch (req.path) { - - case "/": - res.sendFile(dirPublic + "index.html"); - return; - - case "/scripts": - res.sendFile(dirPublic + "scripts.html"); - return; - - case "/strip_setup": - res.sendFile(dirPublic + "strip_setup.html"); - return; - - case "/neo_ide": - res.sendFile(dirPublic + "neo_ide.html"); - return; - - case "/logviewer": - res.sendFile(dirPublic + "logviewer.html"); - return; - - case "/settings": - res.sendFile(dirPublic + "settings.html"); - return; - - } - - next() - } -} diff --git a/src/neoRuntime/index.js b/src/neoRuntime/index.js deleted file mode 100644 index d34f4b4..0000000 --- a/src/neoRuntime/index.js +++ /dev/null @@ -1,232 +0,0 @@ -let fs = require("fs-extra"); -let path = require("path"); -let compileRun = require("../compileAndRun"); - -let listDirsInDir = p => fs.readdirSync(p).filter(f => fs.statSync(path.join(p, f)).isDirectory()); - -class neoRuntime { - - constructor(dirUsrData) { - this._proc = undefined; - this._cScript = "None"; - this._cScriptHasExited = false; - this._dirUsrData = dirUsrData; - } - - status() { - return { - "currentScript": this._cScript, - "scriptIsExited": this._cScriptHasExited, - "uptime": process.uptime() - }; - } - - listScripts() { - let localScripts = listDirsInDir(this._dirUsrData + "/usrCode/"); - let remoteScripts = listDirsInDir(this._dirUsrData + "/remoteCode/"); - let scriptList = []; - - for (let i = 0; i < localScripts.length; i++) { - if (fs.existsSync(this._dirUsrData + "/usrCode/" + localScripts[i] + "/script.py")) { - scriptList.push({ - "name": localScripts[i], - "loc": "local", - "path": "local/" + localScripts[i], - "lang": "python" - }); - } - } - - for (let i = 0; i < remoteScripts.length; i++) { - if (fs.existsSync(this._dirUsrData + "/remoteCode/" + remoteScripts[i] + "/script.py")) { - scriptList.push({ - "name": remoteScripts[i], - "loc": "remote", - "path": "remote/" + remoteScripts[i], - "lang": "python" - }); - } - } - - return scriptList; - } - - selectScript(path) { - global.log(`Selecting script \"${path}\"`, "event"); - - try { - this._proc.stop(); - } catch (err) { - global.log("Could not kill process: " + err, "error"); - } - - this._cScript = path; - path = path.split("/"); - let location = path.splice(0, 1).toString(); - if (location === "local") { path = this._dirUsrData + "/usrCode/" + path.join("/"); } - if (location === "remote") { path = this._dirUsrData + "/remoteCode/" + path.join("/"); } - //if (location === "base") { path = this._dirUsrData + "/src/compileAndRun/scripts/" + path.join("/"); } // TODO make this do something - - if (!fs.existsSync(path + "/script.py")) { - global.log(`No script file found when selecting script with real path \"${path}\"`, "ERROR"); - return; - } - - fs.removeSync(path + "/build/logs/log"); - - this._cScriptHasExited = false; - this._proc = new compileRun.Python(path, this._dirUsrData); - this._proc.compileAndRun(); - - fs.ensureFileSync(path + "/build/logs/log"); - this._proc.on("stdout::data", (_stdout) => { - fs.appendFile(path + "/build/logs/log", "\n====stdout====\n" + _stdout.toString().replace(/\n$/, "")); - }); - this._proc.on("stderr::data", (_stderr) => { - fs.appendFile(path + "/build/logs/log", "\n====stderr====\n" + _stderr.toString().replace(/\n$/, "")); - }); - this._proc.on("stderr::end", () => { - fs.appendFile(path + "/build/logs/log", "\n"); - }); - this._proc.on("stdout::end", () => { - fs.appendFile(path + "/build/logs/log", "\n"); - }); - this._proc.on("close", (_code) => { - fs.appendFile(path + "/build/logs/log","\n====close====\nScript exited with code " + _code.toString()); - this._cScriptHasExited = true; - }); - - } - - stopScript() { - try { - this._proc.stop(); - return { - success: true - } - } catch (err) { - return { - success: false, - error: {reason: err} - }; - } - } - - deleteScript(path) { - global.log(`Deleting script \"${path}\"`, "DEBUG"); - - let sPath = path.split("/"); - let location = sPath.splice(0, 1).toString(); - if (location === "remote") { - global.log(`Cannot delete remote script ${path}`, "DEBUG"); - return; - } - let absPath = this._dirUsrData + "/usrCode/" + sPath.join("/"); - - if (this._cScriptPath == path) { - try { - this._proc.stop(); - } catch (err) { - global.log("Could not kill process: " + err, "error"); - } - } - - fs.removeSync(absPath); - - } - - createEmptyScript(name) { - global.log(`Creating script with name \"${name}/"`, "DEBUG"); - - let scriptFolderPath = this._dirUsrData + "/usrCode/" + name; - if (fs.existsSync(scriptFolderPath)) { - global.log(`A Script with the name \"${name}\" already exists`, "ERROR"); - return; - } - - fs.ensureDirSync(scriptFolderPath); - fs.writeFile(scriptFolderPath + "/script.py", - "import LuxcenaNeo as neo # Can be imported as LuxcenaNeo as well. but anything else and it will fail...\n\nclass Main(neo.NeoBehaviour):\n\n def onStart(self):\n print (\"Script started\")\n\n def eachSecond(self):\n print (\"A second has passed\")", - (err) => { - if (err) { - global.log("Could not create script.py for profile \"" + name + "\"", "ERROR"); - } else { - global.log("Script \"" + name + "\" created.", "SUCCESS"); - } - } - ); - - } - - getScript(scriptPath) { - let sPath = scriptPath.split("/"); - let location = sPath.splice(0, 1).toString(); - if (location === "remote") { - global.log(`Cannot edit remote script ${path}`, "DEBUG"); - return; - } - let absPath = this._dirUsrData + "/usrCode/" + sPath.join("/"); - - if (!fs.existsSync(absPath + "/script.py")) { - return false; - } - - return { - name : sPath[sPath.length - 1], - files : { - main : fs.readFileSync(absPath + "/script.py", "utf8") - } - }; - } - - saveScript(script, callback) { - if (!fs.existsSync(this._dirUsrData + "/usrCode/" + script.name )) { - callback({success: false, error: {reason: "Script file not found"}}); - } - fs.writeFile(this._dirUsrData + "/usrCode/" + script.name + "/script.py", script.files.main, (err) => { - if (err) { - callback({success: false, error: {reason: err}}); - } else { - callback({success: true}); - } - }); - } - - getScriptOutput(scriptPath) { - if (scriptPath != this._cScript) { - return { - success: false, - error: {reason: "This is not the current running script"} - } - } - - let path = scriptPath.split("/"); - let location = path.splice(0, 1).toString(); - if (location === "local") { path = this._dirUsrData + "/usrCode/" + path.join("/"); } - if (location === "remote") { path = this._dirUsrData + "/remoteCode/" + path.join("/"); } - - if (!fs.existsSync(path + "/build/logs/log")){ - return { - success: false, - error: {reason: "No log file found"} - } - } - - try { - let output = fs.readFileSync(path + "/build/logs/log", "utf8"); - return { - success: true, - output: output - } - } catch (err) { - return { - success: false, - error: {reason: err} - }; - } - } -} - -module.exports = (dirUsrData) => { - return new neoRuntime(dirUsrData); -}; diff --git a/src/public/app.js b/src/public/app.js deleted file mode 100644 index 58af98a..0000000 --- a/src/public/app.js +++ /dev/null @@ -1,19 +0,0 @@ -// General JavaScript -require ("./js/general")(); - -// Page-specific JavaScript -const pageName = document.getElementsByTagName("body")[0].id; -try { - require("./js/" + pageName)(); -} catch (error) { - console.log( - "Something went wrong when loading the js for this page...\n" + - "The pageName is \"" + pageName + "\".\n" + - "If it was excpected to get js for this page, please check the filename, and recompile webpack." - ); -} - -// Require all styles -require("./app.scss"); -// Require font awesome -require("fontawesome"); diff --git a/src/public/app.scss b/src/public/app.scss deleted file mode 100644 index 845d36a..0000000 --- a/src/public/app.scss +++ /dev/null @@ -1,9 +0,0 @@ -// This file is probably not needed anymore, as all the files are loaded directly by our sass-loader -// General scss -@import "./scss/general.scss"; - -// Page-specific css -@import "./scss/scripts.scss"; -@import "./scss/neo_ide.scss"; -@import "./scss/setup.scss"; -@import "./scss/update.scss"; diff --git a/src/public/components/sidenav.js b/src/public/components/sidenav.js deleted file mode 100644 index a69aac5..0000000 --- a/src/public/components/sidenav.js +++ /dev/null @@ -1,24 +0,0 @@ -module.exports = ` - -`; diff --git a/src/public/js/general.js b/src/public/js/general.js deleted file mode 100644 index b0b04d2..0000000 --- a/src/public/js/general.js +++ /dev/null @@ -1,10 +0,0 @@ -let sidenav = require("../components/sidenav"); - -module.exports = () => { - const pageName = document.getElementsByTagName("body")[0].id; - if (pageName == "neo_ide") { return; } - - document.getElementById("sidenav").innerHTML = sidenav; - - M.AutoInit(); -}; diff --git a/src/public/js/index.js b/src/public/js/index.js deleted file mode 100644 index 5b67de2..0000000 --- a/src/public/js/index.js +++ /dev/null @@ -1,68 +0,0 @@ -let socket = io(); - -module.exports = () => { - M.AutoInit(); - setupSocket(); - - setInterval(() => { - socket.emit("GetGeneralInfo"); - }, 500); - - socket.emit("GetLog", {filter: "success error event", entryN: 10}); -}; - -function setupSocket() { - - socket.on("lastLogEntries", (entries) => { - let list = ""; - entries.forEach((entry) => { - list += "" + prettifyType(entry.type) + "" + entry.time + "" + entry.details + ""; - }); - - document.getElementById("log-table-body").innerHTML = list; - M.AutoInit(); - }); - - socket.on("newLogEntry", (entry) => { - // Start with parsing the new entry, no reason to select the DOM-element and stuff, if we are filtering out the entry anyway. - let type = entry.type; - if ( (type.toUpperCase() !== "SUCCESS") && (type.toUpperCase() !== "ERROR") && (type.toUpperCase() !== "EVENT") && (type.toUpperCase() !== "INFO")) { - return; - } - - let logTable = document.getElementById("log-table-body"); - - let LTable = logTable.rows.length; - logTable.deleteRow(LTable - 1); // Since length outputs a 1-based number - - let newEntry = logTable.insertRow(0); - newEntry.insertCell(0).innerHTML = prettifyType(entry.type); - newEntry.insertCell(1).innerHTML = entry.time; - newEntry.insertCell(2).innerHTML = entry.details; - M.AutoInit(); - newEntry.className = "newLogEntry"; - }); - - socket.on("generalInfo", (info) => { - if (info["scriptIsExited"]) { - document.getElementById("currentScript").innerHTML = info["currentScript"] + " (exited)"; - } else { - document.getElementById("currentScript").innerHTML = info["currentScript"]; - } - document.getElementById("uptime").innerHTML = info["uptime"] + " seconds"; - }); - -} - -function prettifyType(type) { - let prettyTable = { - "DEBUG": `😸`, - "INFO": `ℹ️`, - "WARNING": `⚠️`, - "EVENT": `⚡️`, - "SUCCESS": ``, - "ERROR": `🔴`, - "PYTHON": `🐍` - }; - return prettyTable[type]; -} diff --git a/src/public/js/logviewer.js b/src/public/js/logviewer.js deleted file mode 100644 index d376196..0000000 --- a/src/public/js/logviewer.js +++ /dev/null @@ -1,64 +0,0 @@ -let socket = io(); - -module.exports = () => { - M.AutoInit(); - - socket.emit("GetLog", {filter: "success error event debug python info warning", entryN: 1000}); - socket.on("lastLogEntries", (entries) => { - M.toast({html: "Loading log-files..."}); - console.log("Log-entries received: " + entries.length); - let HTMLBasicTable = ""; - let HTMLAdvancedTable = ""; - let HTMLScriptTable = ""; - let HTMLRAWTable = ""; - - entries.forEach((entry) => { - let strHTML = "" + prettifyType(entry.type) + "" + entry.time + "" + entry.details + ""; - - if (entry.type === "SUCCESS") { HTMLBasicTable += strHTML; } - if (entry.type === "ERROR") { HTMLBasicTable += strHTML; } - if (entry.type === "EVENT") { HTMLBasicTable += strHTML; } - if (entry.type === "PYTHON") { HTMLScriptTable += strHTML; } - if (entry.type !== "PYTHON") { HTMLAdvancedTable += strHTML; } - - //HTMLRAWTable += entry.join(" "); - }); - - document.getElementById("log-table-basic").innerHTML = HTMLBasicTable; - document.getElementById("log-table-script").innerHTML = HTMLScriptTable; - document.getElementById("log-table-advanced").innerHTML = HTMLAdvancedTable; - //document.getElementById("log-table-raw").innerHTML = HTMLRAWTable; - - }); - - socket.on("newLogEntry", (entry) => { - if (entry.type === "SUCCESS") { appendEntryToTable("log-table-basic", entry); } - if (entry.type === "ERROR") { appendEntryToTable("log-table-basic", entry); } - if (entry.type === "EVENT") { appendEntryToTable("log-table-basic", entry); } - if (entry.type === "PYTHON") { appendEntryToTable("log-table-script", entry); } - if (entry.type !== "PYTHON") { appendEntryToTable("log-table-advanced", entry); } - }); - -}; - -function appendEntryToTable(tableName, entry) { - let newEntry = document.getElementById(tableName).insertRow(0); - newEntry.insertCell(0).innerHTML = prettifyType(entry.type); - newEntry.insertCell(1).innerHTML = entry.time; - newEntry.insertCell(2).innerHTML = entry.details; - M.AutoInit(); - newEntry.className = "newLogEntry"; -} - -function prettifyType(type) { - let prettyTable = { - "DEBUG": `😸`, - "INFO": `ℹ️`, - "WARNING": `⚠️`, - "EVENT": `⚡️`, - "SUCCESS": ``, - "ERROR": `🔴`, - "PYTHON": `🐍` - }; - return prettyTable[type]; -} \ No newline at end of file diff --git a/src/public/js/neo_ide.js b/src/public/js/neo_ide.js deleted file mode 100644 index 6108be4..0000000 --- a/src/public/js/neo_ide.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = () => { - - -}; \ No newline at end of file diff --git a/src/public/js/scripts.js b/src/public/js/scripts.js deleted file mode 100644 index ccad3cf..0000000 --- a/src/public/js/scripts.js +++ /dev/null @@ -1,71 +0,0 @@ -module.exports = () => { - let socket = io(); - socket.emit("GetScripts", {}); - - socket.on("updatedScriptList", (scriptList) => { - let localScriptsHTML = ""; - let remoteScriptsHTML = ""; - - for (let i = 0; i < scriptList.length; i++) { - if (scriptList[i].loc !== "local") { continue; } - let HTMLElem = "
  • {{script_name}}

    {{badges}}

    {{buttons}}
  • "; - if (scriptList[i].loc === "local") { - HTMLElem = HTMLElem.replace("{{badges}}", ""); - HTMLElem = HTMLElem.replace("{{script_name}}", scriptList[i].name); - HTMLElem = HTMLElem.replace("{{buttons}}", - "play_arrow" + - "edit" + - "delete_forever" - ); - localScriptsHTML += HTMLElem; - } else if (scriptList[i].loc === "remote") { - HTMLElem = HTMLElem.replace("{{badges}}", "GitHub"); - HTMLElem = HTMLElem.replace("{{script_name}}", scriptList[i].name); - remoteScriptsHTML += HTMLElem; - } - } - - document.getElementById("local-scripts").innerHTML = localScriptsHTML; - document.getElementById("remote-scripts").innerHTML = remoteScriptsHTML; - - }); - - /* - The delays here with settimeout, is set to a second deliberately, because, rather than making a whole checking-thing. - We just wait a second, and assume, that if it worked, the change should show now. Else, check the logViewer. - */ - function clickHandler(event) { - let element = event.target.parentElement; - - if (element.className === "selectScript") { - M.toast({html: "Now selecting script: " + element.dataset.path}); - socket.emit("SelectScript", {"scriptPath": element.dataset.path}); - - } else if (element.className === "editScript") { - window.location.href = ( - "http://" + window.location.hostname + ":" + window.location.port + - "/neo_ide?scriptName=" + btoa(element.dataset.path) - ); - - } else if (element.className === "deleteScript") { - if (confirm("Do you really want to delete this script?\n" + element.dataset.path + "\n\nYou can not undo this action, and the script will be lost forever...")) { - M.toast({html: "Trying to create script. If no change after a second. Check the logViewer."}); - socket.emit("DeleteScript", {"scriptPath": element.dataset.path}); - setTimeout(() => {socket.emit("GetScripts", {})}, 1000); - } - - } else if (element.id === "createEmptyScript") { - var scriptName = prompt("Please enter the name of the new script:"); - if (scriptName != null || scriptName != "") { - M.toast({html: "Trying to create script. If no change after a second. Check the logViewer."}); - socket.emit("CreateEmptyScript", {"scriptName": scriptName}); - setTimeout(() => {socket.emit("GetScripts", {})}, 1000); - } - - } - - } - - addEventListener("click", clickHandler, false); - -}; diff --git a/src/public/scss/general.scss b/src/public/scss/general.scss deleted file mode 100644 index cbbc654..0000000 --- a/src/public/scss/general.scss +++ /dev/null @@ -1,44 +0,0 @@ -.general { - - header, main, footer { - padding-left: 300px; - } - - header .brand-logo { - padding-left: 15px; - } - - @media only screen and (max-width : 992px) { - header, main, footer { - padding-left: 0; - } - } - - .user-view { - margin-top: 5px; - height: 150px; - } - - .user-view .background { - margin-left: 85px; - } - - .log-table { - - - - - } - - @keyframes highlightNew { - 0% {background-color:#ffc107;} - 100% {background-color:white;} - } - - .newLogEntry { - -webkit-animation-name: highlightNew; /* Safari 4.0 - 8.0 */ - -webkit-animation-duration: 4s; /* Safari 4.0 - 8.0 */ - animation-name: highlightNew; - animation-duration: 4s; - } -} diff --git a/src/public/scss/neo_ide.scss b/src/public/scss/neo_ide.scss deleted file mode 100644 index fb10885..0000000 --- a/src/public/scss/neo_ide.scss +++ /dev/null @@ -1,254 +0,0 @@ -.neo_ide { - - html, - body { - height: 100%; - font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif; - padding: 0; - margin: 0; - /*margin-top: -20px;*/ - overflow: auto; - } - - #editor { - width: 100%; - height: 100%; - margin: -10px; - } - - .page-container { - display: flex; - flex-direction: column; - justify-content: center; - } - - .nav-container { - width: 100%; - height: 45px; - background-color: #333333; - - ul { - display: flex; - flex-direction: row; - justify-content: space-between; - flex: 1; - align-self: center; - width: 100%; - list-style: none; - } - - ul .left { - color: white; - padding: 15px; - margin-left: -40px; - margin-top: -19px; - font-size: 20px; - } - - ul .center { - color: white; - } - - ul .right { - width: 80px; - margin-right: 174px; - color: white; - } - - ul .right .button { - padding: 15px; - margin-top: -15px; - } - - .fileName { - color: grey; - } - - ul .button { - - } - - ul .button:hover { - background-color: black; - } - } - - @keyframes menuBarIn { - from {height: 0;} - to {height: 25px;} - } - - @keyframes menuBarOut { - from {height: 25px;} - to {height: 0;} - } - - .menuBarIn { - animation-name: menuBarIn; - animation-duration: 0.5s; - height: 25px !important; - } - - .menuBarOut { - animation-name: menuBarOut; - animation-duration: 0.5s; - height: 0px !important; - } - - .menubar-container { - background-color: #4e4e4e; - height: 0; - - ul { - display: flex; - flex-direction: row; - flex: 1; - width: 100%; - list-style: none; - margin-top: 2px; - margin-left: -30px; - } - - .button { - background-color: #3e3e3e; - color: white; - padding: 3px 10px 3px 10px; - margin-right: 4px; - font-size: 13px; - border-radius: 7px; - } - - .button:hover { - background-color: black; - } - - } - - .panel-container { - display: flex; - flex-direction: column; - flex: 1; - overflow: hidden; - - .panel-top { - flex: 0 0 auto; - padding: 10px; - height: 80%; - width: 100%; - white-space: nowrap; - background: #838383; - color: white; - } - - .splitter { - flex: 0 0 auto; - margin-top: -20px; - height: 25px; - background: url(https://raw.githubusercontent.com/RickStrahl/jquery-resizable/master/assets/hsizegrip.png) center center no-repeat #535353; - cursor: row-resize; - } - - .splitter .text { - margin-left: 10px; - margin-top: 4px; - color: #cccccc; - - span { - margin-left: 5px; - } - } - - .panel-bottom { - flex: 1 1 auto; - padding: 10px; - /*min-height: 200px;*/ - background: black; - color: white; - - overflow-y: scroll; - } - - .panel-bottom pre { - margin-top: -10px; - - .stdout { - color: white; - } - - .stderr { - color: red; - } - - .close { - color: yellow; - } - } - } - - label { - font-size: 1.2em; - display: block; - font-weight: bold; - margin: 30px 0 10px; - } - - - - - - .page-loader { - position: absolute; - margin: 0; - padding: 0; - border: 0; - width: 100vw; - height: 100vh; - background-color: #1fa2ed; - color: #fff; - - // LET THE LOADING BEGIN - .loader { - display: flex; - justify-content: center; - flex-flow: nowrap column; - align-items: center; - min-height: 100vh; - } - .loading { - display: flex; - margin: 24px 0; - } - .loading__element { - font: normal 100 2rem/1 Roboto; - letter-spacing: .5em; - } - [class*="el"] { - animation: bouncing 3s infinite ease; - } - - @for $i from 1 through 19 { - $delay: percentage($i); - .el#{$i} { - animation-delay: $delay / 1000% + s; - } - } - - @keyframes bouncing { - 0%, 100% { - transform: scale3d(1,1,1); - } - 50% { - transform: scale3d(0,0,1); - } - } - - .current-event { - color: rgba(255, 255, 255, 0.53); - font: normal 100 1rem/1 Roboto; - letter-spacing: .1em; - } - - } - - -} diff --git a/src/public/scss/scripts.scss b/src/public/scss/scripts.scss deleted file mode 100644 index 400c5f1..0000000 --- a/src/public/scss/scripts.scss +++ /dev/null @@ -1,35 +0,0 @@ -#scripts { - - .script-list .badge { - margin-left: 0; - float: none; - } - - .card { - /*margin-bottom: 60px;*/ - } - - @media only screen and (max-width: 599px) { - .card-action { - border-top: 0 !important; - padding: 0 !important; - height: 0; - transition: padding 0.5s ease 0s, - height 0.5s ease 0s; - } - - .card-action a i { - transform: scale(0); - transition: transform 0.1s ease 0.1s; - } - .card:hover > .card-action { - height: 55px; - padding: 16px 24px !important; - } - - .card:hover > .card-action > a > i { - transform: scale(1); - } - } - -} \ No newline at end of file diff --git a/src/public/scss/setup.scss b/src/public/scss/setup.scss deleted file mode 100644 index e69de29..0000000 diff --git a/src/public/scss/update.scss b/src/public/scss/update.scss deleted file mode 100644 index 8e9d2f2..0000000 --- a/src/public/scss/update.scss +++ /dev/null @@ -1,62 +0,0 @@ -#update { - * { - margin: 0; - padding: 0; - border: 0; - box-sizing: border-box; - &:before, &:after { - box-sizing: inherit; - } - } - html { - width: 100vw; - height: 100vh; - } - body { - background-color: #1fa2ed; - color: #fff; - } - - // LET THE LOADING BEGIN - .start-screen { - display: flex; - justify-content: center; - flex-flow: nowrap column; - align-items: center; - min-height: 100vh; - } - .loading { - display: flex; - margin: 24px 0; - } - .loading__element { - font: normal 100 2rem/1 Roboto; - letter-spacing: .5em; - } - [class*="el"] { - animation: bouncing 3s infinite ease; - } - - @for $i from 1 through 19 { - $delay: percentage($i); - .el#{$i} { - animation-delay: $delay / 1000% + s; - } - } - - @keyframes bouncing { - 0%, 100% { - transform: scale3d(1,1,1); - } - 50% { - transform: scale3d(0,0,1); - } - } - - .current-event { - color: rgba(255, 255, 255, 0.53); - font: normal 100 1rem/1 Roboto; - letter-spacing: .1em; - } - -} diff --git a/src/runtimeData/index.js b/src/runtimeData/index.js deleted file mode 100644 index 8253140..0000000 --- a/src/runtimeData/index.js +++ /dev/null @@ -1,45 +0,0 @@ -let fse = require("fs-extra"); - -class RuntimeData { - - constructor (DirUserData) { - this.lockFile = DirUserData + "/config/runtime.json"; - this.runtimeVars = {}; - - this.readFile(); - this.saveFile(); - }; - - saveFile() { - fse.outputJsonSync(this.lockFile, this.runtimeVars); - } - - readFile() { - try { - this.runtimeVars = fse.readJsonSync(this.lockFile); - } catch (err) { - this.runtimeVars = {}; - } - } - - set (name, value) { - this.runtimeVars[name] = value; - this.saveFile(); - }; - - get (name) { - this.readFile(); - if (typeof (this.runtimeVars[name]) != undefined) { - return this.runtimeVars[name]; - } else { - return false; - } - } - - unset (name) { - delete this.runtimeVars[name]; - } - -} - -module.exports = RuntimeData; diff --git a/src/versionChecker/index.js b/src/versionChecker/index.js deleted file mode 100644 index cee1486..0000000 --- a/src/versionChecker/index.js +++ /dev/null @@ -1,58 +0,0 @@ -let fs = require("fs-extra"); -let url = require("url"); -let request = require('request'); -let exec = require("child_process").exec; - -class versionChecker { - - constructor(configJson, packageJson) { - this.config = JSON.parse(fs.readFileSync(configJson)); - this.CPackageJson = JSON.parse(fs.readFileSync(packageJson)); - - this.version = this.CPackageJson["version"]; - this.repoVersion = this.version; - this.checkFrequency = this.config["checkInterval"] * 100 * 10 * 60 * 60; // takes in hours - this.repoLink = this.CPackageJson["repository"]["url"]; - this.repoBranch = this.config["branch"]; - - this.checkVersion(this); // Because we have to send the reference in our interval, we have to to it here as well - this.updateChecker = setInterval(this.checkVersion, this.checkFrequency, this); // We have to send a reference to this class - } - - checkVersion(parent) { - if (typeof(parent) == "undefined") { parent = this; } - let path = url.parse(parent.repoLink); - let link = "https://raw.githubusercontent.com" + path.pathname + '/' + parent.repoBranch + "/package.json"; - - request.get(link, (error, response, body) => { - if (!error && response.statusCode === 200) { - let remotePackageJSON = JSON.parse(body); - this.repoVersion = remotePackageJSON["version"]; - if (parent.VersionIsNewerThan(this.repoVersion, parent.version)) { - global.log("A new version is available on \"" + parent.repoBranch + "\" (v" + this.repoVersion + ")", "INFO"); - } - } else { - global.log("Could not find latest version! Please check you internet connection.", "ERROR"); - } - }); - } - - VersionIsNewerThan(check, current) { - let checkParts = check.split("."); - let currentParts = current.split("."); - let lCheckParts = checkParts.length; - let lCurrentParts = currentParts.length; - - for (let i = 0; i < lCheckParts; i++) { - if (i >= lCurrentParts) { return true; } - if (Number (checkParts[i]) > Number (currentParts[i])) { return true; } - if (Number (checkParts[i]) < Number (currentParts[i])) { return false; } - } - return false; - } - -} -  -module.exports = (configJson, packageJson) => { - return new versionChecker(configJson, packageJson); -}; -- cgit v1.2.3 From 5cc8e0a8ed605a15b95b707b9d1b805f32271e3f Mon Sep 17 00:00:00 2001 From: Jakob Stendahl Date: Sun, 3 Oct 2021 16:44:59 +0200 Subject: :building_construction: Use UNIX socket for IPC instead of stdin/out --- NeoRuntime/Runtime/luxcena_neo/strip.py | 2 +- NeoRuntime/Runtime/neo_runtime.py | 141 +++++++++++----- src/NeoRuntimeManager/IPC.js | 178 +++++++++++++++++++++ src/NeoRuntimeManager/RuntimeProcess.js | 78 ++------- src/NeoRuntimeManager/index.js | 34 ++-- src/UserData/index.js | 3 + .../MainControls/ControlComponents.svelte | 2 +- 7 files changed, 321 insertions(+), 117 deletions(-) create mode 100644 src/NeoRuntimeManager/IPC.js (limited to 'src') diff --git a/NeoRuntime/Runtime/luxcena_neo/strip.py b/NeoRuntime/Runtime/luxcena_neo/strip.py index a65b3f0..bfe2bbc 100644 --- a/NeoRuntime/Runtime/luxcena_neo/strip.py +++ b/NeoRuntime/Runtime/luxcena_neo/strip.py @@ -59,7 +59,7 @@ class Strip: self.__brightness = 255 self.__actual_brightness = self.__brightness - self.__globvars_path = path.join(path.split(path.dirname(path.abspath(__file__)))[0], "globvars.json") + self.__globvars_path = path.join(path.split(path.dirname(path.abspath(__file__)))[0], "state.json") if path.exists(self.__globvars_path): try: with open(self.__globvars_path, "r") as f: diff --git a/NeoRuntime/Runtime/neo_runtime.py b/NeoRuntime/Runtime/neo_runtime.py index e5941e2..4ecbc97 100644 --- a/NeoRuntime/Runtime/neo_runtime.py +++ b/NeoRuntime/Runtime/neo_runtime.py @@ -11,7 +11,8 @@ import time import threading import select import traceback -from os import path +import socket +from os import path, remove from luxcena_neo.strip import Strip @@ -41,7 +42,6 @@ def init_package(package_path, entry_module, strip): # Make the strip instance available in our modules setattr(module, "strip", strip) - module_entry_instance.declare_variables() return module_entry_instance def exec_module(module_executor_loop_func): @@ -53,10 +53,12 @@ def exec_module(module_executor_loop_func): class NeoRuntime: - def __init__(self, package_path, entry_module, strip_config_file): + def __init__(self, package_path, entry_module, strip_config_file, socket_file): self.__strip = init_strip(strip_config_file) self.__module_entry_instance = init_package(package_path, entry_module, self.__strip) self.__module_th = None + self.__socket_file = socket_file + self.__send_strip_buffer = False def start(self): @@ -66,49 +68,113 @@ class NeoRuntime: # This will run in this thread. print("> Starting to listen on stdin") + self.__s = None try: - self.__command_listener_loop() + self.__bind_socket() + self.__socket_listener() except KeyboardInterrupt: print("Exiting...") except Exception as e: traceback.print_exc() - + finally: + self.__close_socket() + + def __bind_socket(self): + if path.exists(self.__socket_file): + remove(self.__socket_file) + + self.__s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + self.__s.bind(self.__socket_file) + self.__s.listen(1) - def __command_listener_loop(self): + def __socket_listener(self): + self.__s_clients = [] last_send = time.perf_counter() + while True: if not self.__module_th.is_alive(): break - while sys.stdin in select.select([sys.stdin], [], [], 0)[0]: - line = sys.stdin.readline() - if line: - line = line.replace("\n", "") - if (line[0:10] == ":::setvar:"): - name, value = (line.split(" ", 1)[1]).replace("\"", "").split(":", 1) - if name in self.__module_entry_instance.vars: - self.__module_entry_instance.vars[name] = value - elif (line[0:11] == ":::setglob:"): - name, value = (line.split(" ", 1)[1]).replace("\"", "").split(":", 1) - if name == "brightness": - self.__strip.brightness = int(value) - elif name == "power_on": - self.__strip.power_on = value == "true" - else: - print(f"Unknown globvar \"{name}\"") - else: - if (time.perf_counter() - last_send) > 0.5: - _vars = "{" - for name, var in self.__module_entry_instance.vars: - _vars += f" \"{name}\" : {{ \"value\": \"{var.value}\", \"var_type\": \"{var.var_type}\" }}, " - if len(_vars) > 2: - _vars = _vars[0:-2] - _vars += "}" - - globvars = "{ \"power_on\": " + str(self.__strip.power_on).lower() + ", " - globvars += " \"brightness\":" + str(self.__strip.brightness) + " }" - print(f"{{ \":::data:\": {{ \"variables\": {_vars}, \"globvars\": {globvars} }} }}") - last_send = time.perf_counter() + r, w, e = select.select([self.__s, *self.__s_clients], self.__s_clients, [], 0) + + if (time.perf_counter() - last_send) > 0.5: + states = { + "variables": self.__module_entry_instance.var.to_dict(), + "globvars": { + "power_on": self.__strip.power_on, + "brightness": self.__strip.brightness + } + } + buf = bytes([1]) + bytes(json.dumps(states), "ascii") + + for ws in w: + try: + ws.send(buf) + except BrokenPipeError: + self.__s_clients.remove(ws) + ws.close() + + last_send = time.perf_counter() + + for rs in r: + if rs is self.__s: + c, a = self.__s.accept() + self.__s_clients.append(c) + else: + data = rs.recv(128) + if not data: + self.__s_clients.remove(rs) + rs.close() + else: + self.__execute_command(data) + def __close_socket(self): + if (self.__s is None): return + r, w, e = select.select([self.__s, *self.__s_clients], self.__s_clients, [], 0) + for ws in w: + try: + ws.shutdown(socket.SHUT_RDWR) + except BrokenPipeError: + ws.close() + self.__s_clients.remove(ws) + ws.close() + self.__s.close() + + + + def __execute_command(self, command): + """ + command should be of type bytes + first byte indicates command type (currently setglob or setvar) + + for command type 1 + byte 1 indicates which globvar + byte 2 indicates value + for command type 2 + first 32 bytes are the var name + + """ + # print(command.hex(" ")) + if command[0] == 0: + if command[1] == 0: + self.__strip.power_on = (command[2] == 1) + print(f"Strip power: {self.__strip.power_on}") + elif command[1] == 1: + self.__strip.brightness = command[2] + print(f"Strip brightness: {self.__strip.brightness}") + else: + print(f"Unknown globvar {command[1]}.") + elif command[0] == 1: + name = command[3:3+command[1]].decode("ascii") + value = command[3+command[1]:3+command[1]+command[2]].decode("ascii") + if name in self.__module_entry_instance.var: + self.__module_entry_instance.var[name] = value + else: + print(f"Unknown variable {name}") + elif command[0] == 2: + self.__send_strip_buffer = (command[1] == 1) + else: + print("UNKNOWN COMMAND") + def __module_loop(self): self.__module_entry_instance.on_start() @@ -153,11 +219,14 @@ if __name__ == "__main__": parser.add_argument('--strip-config', help='Path to the strip config file.') parser.add_argument('--mode-path', help='Path of the folder the mode is in.') parser.add_argument('--mode-entry', help='Path of the module that is the entry-point of the module.') + parser.add_argument('--socket-file', help='The socket file the runtime will use to allow communication [default: /tmp/neo_runtime.sock].', default='/tmp/neo_runtime.sock') + parser.add_argument('--socket-enable', help='Wether to enable socket communication [default: true].', default=True) args = parser.parse_args() args.strip_config = args.strip_config.replace("\"", "") args.mode_path = args.mode_path.replace("\"", "") args.mode_entry = args.mode_entry.replace("\"", "") + args.socket_file = args.socket_file.replace("\"", "") if not path.exists(args.strip_config): print(f"Strip config not found ({args.strip_config}).") sys.exit(1) @@ -172,6 +241,6 @@ if __name__ == "__main__": print(f"Module : {args.mode_path}/{args.mode_entry}") print(f"> Starting \"{args.mode_path}\" in NeoRuntime.") - runtime = NeoRuntime(args.mode_path, args.mode_entry, args.strip_config) + runtime = NeoRuntime(args.mode_path, args.mode_entry, args.strip_config, args.socket_file) runtime.start() print ("> NeoRuntime exited...") diff --git a/src/NeoRuntimeManager/IPC.js b/src/NeoRuntimeManager/IPC.js new file mode 100644 index 0000000..817c049 --- /dev/null +++ b/src/NeoRuntimeManager/IPC.js @@ -0,0 +1,178 @@ +/** + * This module is used to communicate with a python NeoRuntime instance. + * + * @author jakobst1n. + * @since 3.10.2021 + */ + +const net = require("net"); +let logger = require(__basedir + "/src/logger"); + +/** @type {int} How long wait between each reconnection attempt */ +const RECONNECT_INTERVAL = 1000; +/** @type {Object} ENUM-ish for command that can be sent to neoruntime */ +const COMMAND = Object.freeze({SET_GLOB : 0, + SET_VAR : 1, + SET_SEND_STRIP_BUF: 2}); +/** @type {Object} ENUM-ish for globvars */ +const GLOBVAR = Object.freeze({POWER_ON : 0, + BRIGHTNESS: 1}); +/** @type {Object} ENUM-ish for what type of data neoruntime sends */ +const DATATYPE = Object.freeze({STATES : 1, + STRIP_BUF: 2}); + +/** + * class that will keep a active connection to a socket if possible, and + * automatically reconnect. It will emit events when data is received, + * and it will send commands to the process. */ +class IPC { + + constructor(_socketFile, _eventEmitter) { + this.socketFile = _socketFile; + this.eventEmitter = _eventEmitter; + + this.client; + this.connected = false; + this.reconnectInterval = false; + + this.globvars = {}; + this.variables = {}; + + this.reconnect(); + } + + /** + * If we are not already attempting to reconnect, this will start a + * interval that tries to reconnect. */ + reconnect() { + if (this.reconnectInterval === false) { + this.reconnectInterval = setInterval(this.tryOpenSocketConnection.bind(this), RECONNECT_INTERVAL); + } + } + + /** + * This will attempt to connect to the socket, and then setup all listeners + * if it succedes. */ + tryOpenSocketConnection() { + // logger.info("Attempting to start IPC"); + + this.client = net.createConnection(this.socketFile) + .on('connect', () => { + clearInterval(this.reconnectInterval); + this.reconnectInterval = false; + // logger.info("IPC Connected."); + }) + .on("ready", () => { + this.connected = true; + }) + .on('data', (data) => { + switch (data[0]) { + case DATATYPE.STATES: + let json_data; + try { + json_data = JSON.parse(data.toString("ascii", 1)); + } catch (e) { + logger.warning("Could not parse json data from neoruntime"); + return; + } + + if (json_data.hasOwnProperty("globvars")) { + forEachDiff(json_data["globvars"], this.globvars, (key, newVal) => { + this.eventEmitter.emit("change", key, newVal); + }); + this.globvars = json_data["globvars"]; + } + if (json_data.hasOwnProperty("variables")) { + forEachDiff(json_data["variables"], this.variables, (key, newVal) => { + this.eventEmitter.emit("change", `variable/${key}`, newVal); + }); + this.variables = json_data["variables"]; + } + break; + + default: + logger.info(data); + } + + }) + .on("timeout", () => { + logger.info("IPC Timeout"); + }) + .on("close", (hadError) => { + // logger.info("IPC Close, hadError: ", hadError); + this.connected = false; + this.reconnect(); + }) + .on("end", () => { + // logger.info("IPC End"); + this.connected = false; + }) + .on('error', (data) => { + // logger.info('IPC Server not active.'); + this.connected = false; + this.reconnect(); + }) + ; + } + + /** + * Will send a command to the socket if we have a active connection, + * if not it will just drop the command. there is no queue implemented + * for such events. */ + sendCommand(commandType, name, value) { + if (this.connected) { + let buf = Buffer.allocUnsafe(128); // It's fine, we know what we are doing + // let buf = Buffer.alloc(128); + + switch (commandType) { + case (COMMAND.SET_GLOB): + buf[1] = name; + buf[2] = value; + break; + case (COMMAND.SET_VAR): + if (name.length > 32) { return {success: false, reason: "name too long", detail: "max size of name is 32 bytes"}; } + if (name.length > 93) { return {success: false, reason: "value too long", detail: "max size of value is 93 bytes"}; } + buf[1] = name.length; + buf[2] = value.length; + buf.write(name, 3, name.length, "ascii"); + buf.write(value, 3+name.length, value.length, "ascii"); + break; + case (COMMAND.SET_SEND_STRIP_BUF): + buf[1] = (name) ? 1 : 0; + default: + logger.info(`IPC UNKNOWN COMMANDTYPE ${commandType}`) + return; + } + + buf[0] = commandType; + this.client.write(buf); + return {success: true} + } + return {success: false, reason: "socket not connected", detail: "This usually means the python script is not running"}; + } + +} + +const isObject = v => v && typeof v === 'object'; + +/** + * Will call callback on all the differences between the dicts + */ +function forEachDiff(dict1, dict2, callback) { + for (const key of new Set([...Object.keys(dict1), ...Object.keys(dict2)])) { + if (isObject(dict1[key]) && isObject(dict2[key])) { + if (dict1[key].value !== dict2[key].value) { + callback(key, dict1[key]); + } + } else if (dict1[key] !== dict2[key]) { + if (isObject(dict2[key]) && (dict1[key] == null)) { + dict2[key].value = null; + callback(key, dict2[key]) + } else { + callback(key, dict1[key]); + } + } + } +} + +module.exports = {IPC, COMMAND, GLOBVAR}; \ No newline at end of file diff --git a/src/NeoRuntimeManager/RuntimeProcess.js b/src/NeoRuntimeManager/RuntimeProcess.js index 60f6a28..60c1de9 100644 --- a/src/NeoRuntimeManager/RuntimeProcess.js +++ b/src/NeoRuntimeManager/RuntimeProcess.js @@ -3,10 +3,11 @@ let spawn = require("child_process"); class RuntimeProcess { - constructor(_modePath, _onVarChange, _eventEmitter) { + constructor(_modePath, _eventEmitter) { this.modePath = _modePath; this.logfile = `${this.modePath}/mode.log`; - + this.errfile = `${this.modePath}/mode.error`; + this.stdout = ""; this.stderr = ""; @@ -16,9 +17,6 @@ class RuntimeProcess { this.isRunning = false; this.exitCode = null; - this.variables = {}; - this.globvars = {}; - this.onVarChange = _onVarChange; this.eventEmitter = _eventEmitter; } @@ -29,7 +27,7 @@ class RuntimeProcess { } this.isRunning = true; this.proc = spawn.spawn( - "python3", + "python3", [ "-u", // This makes us able to get real-time output `${__basedir}/NeoRuntime/Runtime/neo_runtime.py`, @@ -44,28 +42,13 @@ class RuntimeProcess { }); fs.ensureFileSync(this.logfile); + fs.ensureFileSync(this.errfile); this.eventEmitter.emit("proc:start"); this.proc.stdout.on('data', (_stdout) => { let stdout_str = _stdout.toString(); - - let regex = /{ ":::data:": { (.*) } }/gi; - let data = stdout_str.match(regex); - stdout_str = stdout_str.replace(regex, () => ""); - - if ((data != null) && (data.length > 0)) { - try { - this.processVarData(data) - } catch {} - } - - if (stdout_str.replace("\n", "").replace(" ", "") == "") { - // In this case, we want to ignore the data. - } else { - // stdout_str = stdout_str.replace(/\n$/, "") - fs.appendFile(this.logfile, "\n====stdout====\n" + stdout_str); - this.eventEmitter.emit("proc:stdout", stdout_str); - } + fs.appendFile(this.logfile, `[${timestamp()}]: ` + stdout_str); + this.eventEmitter.emit("proc:stdout", stdout_str); }); this.proc.stdout.on('end', () => { @@ -73,9 +56,8 @@ class RuntimeProcess { }); this.proc.stderr.on('data', (_stderr) => { - // let stderr_str = _stderr.toString().replace(/\n$/, "") - let stderr_str = _stderr.toString() - fs.appendFile(this.logfile, "\n====stderr====\n" + stderr_str); + let stderr_str = _stderr.toString(); + fs.appendFile(this.errfile, `[${timestamp()}]: ` + stderr_str); this.eventEmitter.emit("proc:stderr", stderr_str); }); @@ -85,7 +67,7 @@ class RuntimeProcess { this.proc.on('close', (code) => { if (code) { - fs.appendFile(this.logfile, "\n====close====\nScript exited with code " + code.toString()); + fs.appendFile(this.logfile, `[${timestamp()}]: ` + "Script exited with code " + code.toString()); } this.eventEmitter.emit("proc:exit", 0); this.isRunning = false; @@ -106,45 +88,15 @@ class RuntimeProcess { console.log(err); } } - - processVarData(data) { - data = JSON.parse(data)[":::data:"]; - if (data.hasOwnProperty("globvars")) { - forEachDiff(data["globvars"], this.globvars, (key, newVal) => { - this.onVarChange("globvars", key, newVal); - }); - this.globvars = data["globvars"]; - } - if (data.hasOwnProperty("variables")) { - forEachDiff(data["variables"], this.variables, (key, newVal) => { - this.onVarChange("variables", key, newVal); - }); - this.variables = data["variables"]; - } - } - } -const isObject = v => v && typeof v === 'object'; - /** - * Will call callback on all the differences between the dicts + * Creates and returns a timestamp that can be used in logfiles. + * + * @return {string} timestamp */ -function forEachDiff(dict1, dict2, callback) { - for (const key of new Set([...Object.keys(dict1), ...Object.keys(dict2)])) { - if (isObject(dict1[key]) && isObject(dict2[key])) { - if (dict1[key].value !== dict2[key].value) { - callback(key, dict1[key]); - } - } else if (dict1[key] !== dict2[key]) { - if (isObject(dict2[key]) && (dict1[key] == null)) { - dict2[key].value = null; - callback(key, dict2[key]) - } else { - callback(key, dict1[key]); - } - } - } +function timestamp() { + return (new Date()).toISOString(); } module.exports = RuntimeProcess; diff --git a/src/NeoRuntimeManager/index.js b/src/NeoRuntimeManager/index.js index 62acb8a..6238323 100644 --- a/src/NeoRuntimeManager/index.js +++ b/src/NeoRuntimeManager/index.js @@ -8,6 +8,7 @@ const fs = require("fs"); const fsPromises = fs.promises; const RuntimeProcess = require("./RuntimeProcess"); +const IPC = require("./IPC"); let logger = require(__basedir + "/src/logger"); const EventEmitter = require('events'); @@ -20,6 +21,8 @@ let modeId = null; let modeExitCode = 0; /** @type {RuntimeProcess} This is the current RuntimeProcess instance */ let runtimeProcess = null; +/** @type {IPC} The IPC instance, used to communicate with the script */ +let ipc = null; /** @type {EventEmitter} This is used to emit events when things change */ const eventEmitter = new EventEmitter(); /** @type {boolean} If this is true, we will not do things the usual way */ @@ -83,13 +86,6 @@ function setMode(_modeId) { return {success: false, reason: "unknown modeId"}; } logger.info(`Changing mode to "${_modeId}".`); - - let globvarsTmp = {}; - let variablesTmp = {}; - if (runtimeProcess != null) { - globvarsTmp = runtimeProcess.globvars; - variablesTmp = runtimeProcess.variables; - } stopMode(); @@ -97,10 +93,9 @@ function setMode(_modeId) { neoModules.userData.config.activeMode = modeId; eventEmitter.emit("change", "mode", modeId); - runtimeProcess = new RuntimeProcess(getModePath(_modeId), onVariableChange, eventEmitter); - runtimeProcess.globvars = globvarsTmp; - runtimeProcess.variables = variablesTmp; + runtimeProcess = new RuntimeProcess(getModePath(_modeId), eventEmitter); startMode(); + return {success: true} }; @@ -194,7 +189,7 @@ function onVariableChange(location, name, newValue) { */ function getGlobvars() { if (!modeRunning()) { return {}; } - return runtimeProcess.globvars; + return ipc.globvars; } /** @@ -207,8 +202,15 @@ function getGlobvars() { */ function setGlobvar(name, value) { if (!modeRunning()) { return; } - runtimeProcess.proc.stdin.write(`:::setglob: ${name}:${value}\n`); - return {success: true} + + switch(name) { + case "power_on": + return ipc.sendCommand(IPC.COMMAND.SET_GLOB, IPC.GLOBVAR.POWER_ON, (value) ? 1 : 0); + case "brightness": + return ipc.sendCommand(IPC.COMMAND.SET_GLOB, IPC.GLOBVAR.BRIGHTNESS, value); + default: + return {success: false, reason: "unknown globvar", detail: name}; + } } /** @@ -218,7 +220,7 @@ function setGlobvar(name, value) { */ function getVariables() { if (!modeRunning()) { return {}; } - return runtimeProcess.variables; + return ipc.variables; } /** @@ -231,8 +233,7 @@ function getVariables() { */ function setVariable(name, value) { if (!modeRunning()) { return; } - runtimeProcess.proc.stdin.write(`:::setvar: ${name}:${value}\n`); - return {success: true} + return ipc.sendCommand(IPC.COMMAND.SET_VAR, name, value); } /** @@ -281,6 +282,7 @@ function stopDebugger() { module.exports = (_neoModules) => { neoModules = _neoModules; + ipc = new IPC.IPC(neoModules.userData.config.neoRuntimeIPC.socketFile, eventEmitter); return { event: eventEmitter, modes: listModes, diff --git a/src/UserData/index.js b/src/UserData/index.js index e5318c9..704c5d5 100644 --- a/src/UserData/index.js +++ b/src/UserData/index.js @@ -40,6 +40,9 @@ function ensureMainConfig() { if (config.DiscoveryServer.address == null) { config.DiscoveryServer.address = "https://erj46s.deta.dev"; } if (config.DiscoveryServer.broadcastSelf == null) { config.DiscoveryServer.broadcastSelf = false; } + if (config.neoRuntimeIPC == null) { config.neoRuntimeIPC = {}; } + if (config.neoRuntimeIPC.socketFile == null) { config.neoRuntimeIPC.socketFile = "/tmp/neo_runtime.sock"; } + fse.writeFileSync(__datadir + "/config/config.ini", ini.encode(config)) } diff --git a/src_frontend/Components/MainControls/ControlComponents.svelte b/src_frontend/Components/MainControls/ControlComponents.svelte index 5f6d165..65bd1c4 100644 --- a/src_frontend/Components/MainControls/ControlComponents.svelte +++ b/src_frontend/Components/MainControls/ControlComponents.svelte @@ -45,7 +45,7 @@ } name = name.replace("variable/", ""); - switch (value.var_type) { + switch (value.type) { case "COLOR": if (value.value == null) { delete colorVariables[name]; -- cgit v1.2.3 From 950d093ca54366ce49164370325062afd061cf9d Mon Sep 17 00:00:00 2001 From: Jakob Stendahl Date: Sun, 3 Oct 2021 17:06:28 +0200 Subject: :bug: Fix small auth bug --- src/SocketIO/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/SocketIO/index.js b/src/SocketIO/index.js index fdaad56..8d06947 100644 --- a/src/SocketIO/index.js +++ b/src/SocketIO/index.js @@ -82,7 +82,7 @@ function createOpenSocketNamespace(io) { } session_tokens[token] = { - expire: (~~(Date.now()) / 1000)+(86400), + expire: (~~Date.now())+(86400), host: socket.handshake.headers.host, user: {username: user.username} }; -- cgit v1.2.3 From 8fe3b246a12d409b0819b574a050b64ca96ce251 Mon Sep 17 00:00:00 2001 From: jakobst1n Date: Sun, 3 Oct 2021 18:36:12 +0200 Subject: :boom: Change paths to be in normal linux locations --- app.js | 27 +++--- bin/install.sh | 142 +++++++------------------------- bin/luxcena-neo-cli.sh | 41 ++++++++- bin/luxcena-neo.service | 6 +- bin/luxcena-neo.sh | 4 +- src/NeoRuntimeManager/RuntimeProcess.js | 4 +- src/NeoRuntimeManager/index.js | 8 +- src/SSLCert/index.js | 6 +- src/SelfUpdater/index.js | 4 +- src/SocketIO/index.js | 4 +- src/UserData/index.js | 48 +++++------ 11 files changed, 124 insertions(+), 170 deletions(-) (limited to 'src') diff --git a/app.js b/app.js index 0196645..30146ea 100644 --- a/app.js +++ b/app.js @@ -2,22 +2,27 @@ let fse = require("fs-extra"); let events = require('events'); // Firstly we set up all globals, check that the usrData dir exists, if not, we run the setup -let userDir = "/home/lux-neo"; -if (process.argv.length >= 3) { userDir = process.argv[2]; } -if (!fse.existsSync(userDir + "/userdata/")) { +global.__appdir = "/opt/luxcena-neo"; +global.__configdir = "/etc/luxcena-neo"; +global.__datadir = "/var/luxcena-neo"; +global.__logdir = "/var/log/luxcena-neo"; + +if ((process.argv.length >= 3) && (process.argv[2] == "dev")) { + global.__appdir = __dirname; + global.__configdir = __dirname + "/tmp/config"; + global.__datadir = __dirname + "/tmp/userdata"; + global.__logdir = __dirname + "/tmp/logs"; +} +if (!fse.existsSync(global.__datadir)) { console.log(`CRITICAL UserDir not found '${userDir}'! Exiting...`); process.exit(1); } -// Global path variables -global.__basedir = __dirname + ""; -global.__datadir = userDir + "/userdata"; -global.__logdir = userDir + "/logs"; // global eventEmitter global.__event = new events.EventEmitter(); // Secondly we setup the logger, -let logger = require("./src/logger"); +let logger = require("./src/Logger"); logger.info("Starting Luxcena-Neo..."); let neoModules = {}; @@ -33,14 +38,14 @@ let express = require("express"); let https = require("https"); let app = express(); let server = https.createServer({ - key: fse.readFileSync(__datadir + "/config/certs/privkey.pem"), - cert: fse.readFileSync(__datadir + "/config/certs/cert.pem") + key: fse.readFileSync(__configdir + "/certs/privkey.pem"), + cert: fse.readFileSync(__configdir + "/certs/cert.pem") }, app ); let io = require("socket.io")(server); require("./src/SocketIO")(neoModules, io); -app.use("/", express.static(__basedir + "/public")); +app.use("/", express.static(__appdir + "/public")); server.listen(neoModules.userData.config.HTTP.port, () => { let host = server.address().address; diff --git a/bin/install.sh b/bin/install.sh index cd4f07b..a7b811b 100755 --- a/bin/install.sh +++ b/bin/install.sh @@ -5,9 +5,6 @@ printf '%s\n' "Luxcena-neo Installer" tput sgr0 printf '\e[93m%s\e[0m\n\n' "---------------------" -LOG="/tmp/luxcena-neo.install.log" -echo "Starting Luxcena-neo installer..." > $LOG - if [ "$EUID" -ne 0 ]; then echo "You need to run this script as root." echo "Try running with 'sudo ./bin/install.sh'" @@ -16,139 +13,56 @@ fi function die() { tput setaf 1 - printf "\n\nInstall failed.\n" - printf "Check the logfile at '/tmp/lucxena-neo.install.log'.\n" - printf "Use this command to see the last 30 lines of the file;\n" - printf " tail -n 30 /tmp/luxcena-neo.install.log" + printf "\n\nInstall failed, successfull steps not reversed.\n" tput sgr0 exit 1 } -function dlgYN() { - tput sc - tput setaf 4 - printf "$1 (y/n)? " - while : - do - read -n 1 -p "" YNQuestionAnswer - if [[ $YNQuestionAnswer == "y" ]]; then - tput rc; tput el - printf ". $1?: \e[0;32mYes\e[0m\n" - tput sc - eval $2=1 # Set parameter 2 of input to the return value - break - elif [[ $YNQuestionAnswer == "n" ]]; then - tput rc; tput el - printf ". $1?: \e[0;31mNo\e[0m\n" - eval $2=0 # Set parameter 2 of input to the return value - break - fi - done -} - -# Update system -dlgYN ". Update your system" res -if [ $res -eq 1 ]; then - tput sc - apt-get -y update &>> $LOG || die - apt-get -y upgrade &>> $LOG || die - tput rc; tput ed -fi - -# Install packages -dlgYN ". Install required packages" res -if [ $res -eq 1 ]; then - tput sc - apt-get -y install nodejs scons python-dev swig &>> $LOG || die - if [ $? -eq 0 ]; then - tput rc; tput ed - printf "✓" - else - printf "\nInstall failed.\n" - exit 1 - fi -else - tput setaf 2 - printf " We are now assuming that all the following packages exists on your system:\n" - printf " nodejs scons python-dev swig\n" - tput sgr0 -fi - -# Install led-library -dlgYN ". Install jgarff's rpi_ws281x library" res -if [ $res -eq 1 ]; then - tput sc - git clone https://github.com/jgarff/rpi_ws281x /tmp/rpi_ws281x # TODO CHANGE PATH - python /tmp/rpi_ws281x/python/setup.py install # TODO CHANGE PAHT - if [ $? -eq 0 ]; then - tput rc; tput ed - printf "✓" - else - printf "\nInstall failed.\n" - exit 1 - fi -fi - -tput setaf 4 -printf ". Installing the app itself...\n" -tput sgr0 - # Create user 'luxcena-neo' tput setaf 8 -printf '%s\n' " - Creating user 'lux-neo'..." +printf '%s\n' "- Creating user 'lux-neo'..." tput sgr0 username="lux-neo" egrep "^$username" /etc/passwd >/dev/null if [ $? -eq 0 ]; then - echo "User already exists, continuing..." + echo "User already exists, continuing..." else - #pass=$(perl -e 'print crypt($ARGV[0], "password")' $password) - useradd -m $username &>> $LOG || die + useradd -m $username || die fi +usermod -a -G gpio $username +usermod -a -G spi $username # First we make our directories tput setaf 8 -printf '%s\n' " - Making app-dir (/bin/luxcena-neo)..." -tput sgr0 -userDir=$(eval echo "~$username") -#mkdir -p "$userDir/install" &>> $LOG || die -#chown $username:$username "$userDir/install" &>> $LOG || die -mkdir -p "$userDir/src" &>> $LOG || die -chown $username:$username "$userDir/src" &>> $LOG || die -mkdir -p "$userDir/userdata" &>> $LOG || die -chown $username:$username "$userDir/userdata" &>> $LOG || die - -# Third we copy the source into the correct swap-folder -tput setaf 8 -printf '%s\n' " - Copying sourceCode to app-dir..." +printf '%s\n' "- Making directories..." tput sgr0 -cp -r . "$userDir/src" &>> $LOG || die -chown -R $username:$username "$userDir/src" &>> $LOG || die - -# fourth we run npm i -tput setaf 8 -printf '%s\n' " - Running npm i..." -tput sgr0 -tput sc -export NODE_ENV=production &>> $LOG || die -runuser -l $username -c 'npm --prefix ~/src install ~/src --only=production' &>> $LOG || die # This is probably a bit overkill to have --only=... but better safe than sorry? -tput rc; tput ed +[ -d "/opt/luxcena-neo/" ] && echo "Seems like luxcena-neo is already installed, please do update instead" && die +mkdir -p "/opt/luxcena-neo" || die +chown $username:$username "/opt/luxcena-neo" || die +mkdir -p "/var/luxcena-neo" || die +chown $username:$username "/var/luxcena-neo" || die +mkdir -p "/etc/luxcena-neo" || die +chown $username:$username "/etc/luxcena-neo" || die +mkdir -p "/var/log/luxcena-neo" || die +chown $username:$username "/var/log/luxcena-neo" || die + +printf '%s' "Which branch do you want to install (default: master)? " +read BRANCH +if [ -z "$BRANCH" ]; then + BRANCH="master" +fi -# fourth we copy the cli to our bin folder +# Get source code tput setaf 8 -printf '%s\n' " - Adding cli-script..." +printf '%s\n' "- Fetch source code..." tput sgr0 -cp bin/luxcena-neo-cli.sh /usr/bin/luxcena-neo-cli.sh &>> $LOG || die -ln -sf /usr/bin/luxcena-neo-cli.sh /usr/bin/lux-neo &>> $LOG || die -tput rc; tput ed +runuser -l $username -c "git clone -b $BRANCH https://github.com/jakobst1n/luxcena-neo /opt/luxcena-neo/" || die -# Fifth we add the service files +# Install all packages, build the app, and prepare everything tput setaf 8 -printf '%s\n' " - Adding service-file to systemd..." +printf '%s\n' "- Running installer (updater) from newly fetched source code..." tput sgr0 -cp bin/luxcena-neo.service /etc/systemd/system/luxcena-neo.service &>> $LOG || die -systemctl daemon-reload &>> $LOG || die +/opt/luxcena-neo/bin/luxcena-neo-cli.sh update || die # Installation is done! printf '\n\e[5m%s\e[0m\n' "🎉Luxcena-Neo is now installed🎉" -printf 'You can now delete this folder' diff --git a/bin/luxcena-neo-cli.sh b/bin/luxcena-neo-cli.sh index defb766..b3c6553 100755 --- a/bin/luxcena-neo-cli.sh +++ b/bin/luxcena-neo-cli.sh @@ -65,14 +65,49 @@ if [ "$action" == "update" ]; then exit 1 fi + # Stop the service if it is running already systemctl stop luxcena-neo - runuser -l 'lux-neo' -c 'git -C ~/src pull' + # Go to source code directory + WDIR="/opt/luxcena-neo" + #cd "$WDIR" + + # Fetch newest changes on branch + runuser -l 'lux-neo' -c "git -C $WDIR pull" || die + + # Add node repo + curl -fsSL https://deb.nodesource.com/setup_14.x | bash - || die + + # Make sure nodejs and prerequisites is installed + apt install nodejs python-pip || die + + # Make sure we have python virtualenv installed + pip3 install virtualenv || die + + # Create and configure python virtualenv + runuser -l 'lux-neo' -c "rm -rf $WDIR/NeoRuntime/Runtime/venv" || die + runuser -l 'lux-neo' -c "virtualenv -p /usr/bin/python3 $WDIR/NeoRuntime/Runtime/venv" || die + runuser -l 'lux-neo' -c "source $WDIR/NeoRuntime/Runtime/venv/bin/activate && pip install rpi_ws281x" || die + + # Build and run all npm scripts if [ "$2" != "skipNode" ]; then - runuser -l 'lux-neo' -c 'export NODE_ENV=production; npm --prefix ~/src install ~/src --only=production' + runuser -l 'lux-neo' -c "export NODE_ENV=development; npm --prefix $WDIR install $WDIR" || die fi + ##runuser -l 'lux-neo' -c "cd $WDIR && npm run build:frontend" || die + ##runuser -l 'lux-neo' -c "cd $WDIR && npm run build:fontawesome" || die + ##runuser -l 'lux-neo' -c "cd $WDIR && npm run build:dialog-polyfill" || die + runuser -l 'lux-neo' -c "npm --prefix \"$WDIR\" run build:frontend" || die + runuser -l 'lux-neo' -c "npm --prefix \"$WDIR\" run build:fontawesome" || die + runuser -l 'lux-neo' -c "npm --prefix \"$WDIR\" run build:dialog-polyfill" || die + + + # Install new cli script + cp /opt/luxcena-neo/bin/luxcena-neo-cli.sh /usr/bin/luxcena-neo-cli.sh || die + + # Install updated systemd script + cp /opt/luxcena-neo/bin/luxcena-neo.service /etc/systemd/system/luxcena-neo.service || die + systemctl daemon-reload || die - cp /home/lux-neo/src/bin/luxcena-neo-cli.sh /usr/bin/luxcena-neo-cli.sh printf "Update complete.\n" systemctl start luxcena-neo exit 0 diff --git a/bin/luxcena-neo.service b/bin/luxcena-neo.service index efea1ad..b4115be 100644 --- a/bin/luxcena-neo.service +++ b/bin/luxcena-neo.service @@ -2,13 +2,13 @@ Description=Luxcena Neo [Service] -ExecStart=/home/lux-neo/src/bin/luxcena-neo.sh +ExecStart=/opt/luxcena-neo/bin/luxcena-neo.sh Restart=always RestartSec=10 Environment=PATH=/usr/bin:/usr/local/bin -Environment=NODE_ENV=production -WorkingDirectory=/home/lux-neo/src/ +Environment=NODE_ENV=development +WorkingDirectory=/opt/luxcena-neo/ [Install] WantedBy=multi-user.target diff --git a/bin/luxcena-neo.sh b/bin/luxcena-neo.sh index fc41f75..a861d87 100755 --- a/bin/luxcena-neo.sh +++ b/bin/luxcena-neo.sh @@ -5,5 +5,5 @@ # the server needs root as well. #runuser -l pi -c "export NODE_ENV=production; node ~/luxcena-neo-install/src/app.js" -export NODE_ENV=production -node /home/lux-neo/src/app.js >> /home/lux-neo/logs/service.log +export NODE_ENV=development +node /opt/luxcena-neo/app.js >> /var/log/luxcena-neo/service.log diff --git a/src/NeoRuntimeManager/RuntimeProcess.js b/src/NeoRuntimeManager/RuntimeProcess.js index 60f6a28..0058382 100644 --- a/src/NeoRuntimeManager/RuntimeProcess.js +++ b/src/NeoRuntimeManager/RuntimeProcess.js @@ -32,8 +32,8 @@ class RuntimeProcess { "python3", [ "-u", // This makes us able to get real-time output - `${__basedir}/NeoRuntime/Runtime/neo_runtime.py`, - `--strip-config="${__datadir}/config/strip.ini"`, + `${__appdir}/NeoRuntime/Runtime/neo_runtime.py`, + `--strip-config="${__configdir}/strip.ini"`, `--mode-path="${this.modePath}"`, `--mode-entry=script` ] diff --git a/src/NeoRuntimeManager/index.js b/src/NeoRuntimeManager/index.js index 62acb8a..e4718e4 100644 --- a/src/NeoRuntimeManager/index.js +++ b/src/NeoRuntimeManager/index.js @@ -8,7 +8,7 @@ const fs = require("fs"); const fsPromises = fs.promises; const RuntimeProcess = require("./RuntimeProcess"); -let logger = require(__basedir + "/src/logger"); +let logger = require(__appdir + "/src/Logger"); const EventEmitter = require('events'); /** @type {object} this should be a pointer to a object referencing all neoModules (see app.js) */ @@ -51,7 +51,7 @@ function isMode(path) { */ function listModes() { let modeDirs = [ - ["builtin/", fs.readdirSync(__basedir + "/NeoRuntime/builtin")], + ["builtin/", fs.readdirSync(__appdir + "/NeoRuntime/builtin")], ["remote/", fs.readdirSync(__datadir + "/remoteCode")], ["user/", fs.readdirSync(__datadir + "/userCode")] ] @@ -167,7 +167,7 @@ function getModePath(modeId) { let location = path.splice(0, 1).toString(); if (location === "user") { path = __datadir + "/userCode/" + path.join("/"); } if (location === "remote") { path = __datadir + "/remoteCode/" + path.join("/"); } - if (location === "builtin") { path = __basedir + "/NeoRuntime/builtin/" + path.join("/"); } + if (location === "builtin") { path = __appdir + "/NeoRuntime/builtin/" + path.join("/"); } return path; } @@ -306,4 +306,4 @@ module.exports = (_neoModules) => { startDebugger, stopDebugger, saveModeCode, startMode, stopMode, restartMode } -}; \ No newline at end of file +}; diff --git a/src/SSLCert/index.js b/src/SSLCert/index.js index 6dad579..d235c9b 100644 --- a/src/SSLCert/index.js +++ b/src/SSLCert/index.js @@ -7,7 +7,7 @@ * @author jakobst1n. * @since 14.16.2019 */ - let logger = require(__basedir + "/src/logger"); + let logger = require(__appdir + "/src/Logger"); const fs = require("fs"); const { execSync } = require("child_process"); @@ -20,7 +20,7 @@ var neoModules; class CertMon { constructor(configPath, certPath, httpsConfig) { - this.certPath = __datadir + "/config/certs/"; + this.certPath = __configdir + "/certs/"; let valid = this.checkValidity(); if (!valid) { @@ -141,4 +141,4 @@ module.exports = (_neoModules) => { neoModules = _neoModules; return new CertMon(); }; - \ No newline at end of file + diff --git a/src/SelfUpdater/index.js b/src/SelfUpdater/index.js index 3c5546b..5a9baa3 100644 --- a/src/SelfUpdater/index.js +++ b/src/SelfUpdater/index.js @@ -2,14 +2,14 @@ let fs = require("fs-extra"); let url = require("url"); let request = require('request'); let exec = require("child_process").exec; -let logger = require(__basedir + "/src/logger"); +let logger = require(__appdir + "/src/Logger"); let neoModules; class VersionChecker { constructor() { - this.CPackageJson = JSON.parse(fs.readFileSync(__basedir + "/package.json")); + this.CPackageJson = JSON.parse(fs.readFileSync(__appdir + "/package.json")); this.version = this.CPackageJson["version"]; this.repoLink = this.CPackageJson["repository"]["url"]; diff --git a/src/SocketIO/index.js b/src/SocketIO/index.js index fdaad56..e20460d 100644 --- a/src/SocketIO/index.js +++ b/src/SocketIO/index.js @@ -8,7 +8,7 @@ * @since 19.12.2019 */ -let logger = require(__basedir + "/src/logger"); +let logger = require(__appdir + "/src/Logger"); var exec = require('child_process').exec; var CryptoJS = require("crypto-js"); let fs = require("fs"); @@ -350,4 +350,4 @@ module.exports = (_neoModules, io) => { authorizedNamespace: createAuthorizedNamespace(io) } }; - \ No newline at end of file + diff --git a/src/UserData/index.js b/src/UserData/index.js index e5318c9..4684a75 100644 --- a/src/UserData/index.js +++ b/src/UserData/index.js @@ -6,7 +6,7 @@ * @since 19.12.2019 */ -let logger = require(__basedir + "/src/logger"); +let logger = require(__appdir + "/src/Logger"); let fse = require("fs-extra"); let ini = require('ini'); @@ -16,7 +16,7 @@ let neoModules; * This method will ensure that all required fields are in config.ini */ function ensureMainConfig() { - var config = ini.decode(fse.readFileSync(__datadir + "/config/config.ini", 'utf-8')) + var config = ini.decode(fse.readFileSync(__configdir + "/config.ini", 'utf-8')) if (config.instanceName == null) { config.instanceName = "neoStrip"; } if (config.activeMode == null) { config.activeMode = "builtin/static"; } @@ -40,14 +40,14 @@ function ensureMainConfig() { if (config.DiscoveryServer.address == null) { config.DiscoveryServer.address = "https://erj46s.deta.dev"; } if (config.DiscoveryServer.broadcastSelf == null) { config.DiscoveryServer.broadcastSelf = false; } - fse.writeFileSync(__datadir + "/config/config.ini", ini.encode(config)) + fse.writeFileSync(__configdir + "/config.ini", ini.encode(config)) } /** * This method will ensure that all required fields are in config.ini */ function ensureStripConfig() { - var config = ini.decode(fse.readFileSync(__datadir + "/config/strip.ini", 'utf-8')) + var config = ini.decode(fse.readFileSync(__configdir + "/strip.ini", 'utf-8')) if (config.DEFAULT == null) { config.DEFAULT = {}; } if (config.DEFAULT.led_pin == null) { config.DEFAULT.led_pin = 18; } @@ -58,7 +58,7 @@ function ensureStripConfig() { if (config.DEFAULT.segments == null) { config.DEFAULT.segments = ""; } if (config.DEFAULT.matrix == null) { config.DEFAULT.matrix = ""; } - fse.writeFileSync(__datadir + "/config/strip.ini", ini.encode(config)) + fse.writeFileSync(__configdir + "/strip.ini", ini.encode(config)) } /** @@ -70,24 +70,24 @@ function init() { logger.info("Ensuring all folder in UserDir exists..."); fse.ensureDirSync(__datadir + "/"); - fse.ensureDirSync(__datadir + "/config/"); - fse.ensureDirSync(__datadir + "/config/certs"); + fse.ensureDirSync(__configdir); + fse.ensureDirSync(__configdir + "/certs"); fse.ensureDirSync(__datadir + "/userCode/"); fse.ensureDirSync(__datadir + "/remoteCode/"); // Generate config-files - if (!fse.existsSync(__datadir + "/config/config.ini")) { - fse.closeSync(fse.openSync(__datadir + "/config/config.ini", 'w')); + if (!fse.existsSync(__configdir + "/config.ini")) { + fse.closeSync(fse.openSync(__configdir + "/config.ini", 'w')); } ensureMainConfig(); - if (!fse.existsSync(__datadir + "/config/strip.ini")) { - fse.closeSync(fse.openSync(__datadir + "/config/strip.ini", 'w')); + if (!fse.existsSync(__configdir + "/strip.ini")) { + fse.closeSync(fse.openSync(__configdir + "/strip.ini", 'w')); } ensureStripConfig(); - if (!fse.existsSync(__datadir + "/config/users.ini")) { - fse.writeFileSync(__datadir + "/config/users.ini", ini.encode({ + if (!fse.existsSync(__configdir + "/users.ini")) { + fse.writeFileSync(__configdir + "/users.ini", ini.encode({ "neo": { "password": "5adbc90fb4716fff62d9cf634837e22f29b011803ba29cee51f921b920fa941651737bd15d00dc72e4cbeee5e64e06ec99cc50ea917285a029797a98740cce0f", "salt": "59b6de1040f3ae3c63de984ca5d61ef46f41dc6ecead3a9d5dab69f0bb3636aa49017e179b74dbcdb407f62bc139a7d55aa78fe2bbdd5327609ea124b2fa03b1" @@ -193,11 +193,11 @@ function getFullConfig(file, addSetters=true) { * @return {object} Standardform return object */ function saveUser(username, salt, password) { - let config = ini.decode(fse.readFileSync(__datadir + "/config/users.ini", 'utf-8')) + let config = ini.decode(fse.readFileSync(__configdir + "/users.ini", 'utf-8')) config[username] = {} config[username].salt = salt config[username].password = password - fse.writeFileSync(__datadir + "/config/users.ini", ini.encode(config)) + fse.writeFileSync(__configdir + "/users.ini", ini.encode(config)) return {success: true} } @@ -207,7 +207,7 @@ function getFullConfig(file, addSetters=true) { * @return {object} with username, salt and hash properties. */ function getUser(username) { - let config = ini.decode(fse.readFileSync(__datadir + "/config/users.ini", 'utf-8')) + let config = ini.decode(fse.readFileSync(__configdir + "/users.ini", 'utf-8')) if (Object.prototype.hasOwnProperty.call(config, username)) { return {...config[username], username: username} } @@ -220,7 +220,7 @@ function getUser(username) { * @return {array} usernames */ function getUsers() { - let config = ini.decode(fse.readFileSync(__datadir + "/config/users.ini", "utf-8")); + let config = ini.decode(fse.readFileSync(__configdir + "/users.ini", "utf-8")); let users = []; for (const username of Object.keys(config)) { users.push(username); @@ -234,11 +234,11 @@ function getUsers() { * @return {object} Standardform success object. */ function deleteUser(username) { - let config = ini.decode(fse.readFileSync(__datadir + "/config/users.ini", 'utf-8')) + let config = ini.decode(fse.readFileSync(__configdir + "/users.ini", 'utf-8')) if (config.length <= 1) { return {success: false, reason: "cannot delete only user"}; } if (!Object.prototype.hasOwnProperty.call(config, username)) { return {success: false, reason: "user not found", detail: username}; } delete config[username]; - fse.writeFileSync(__datadir + "/config/users.ini", ini.encode(config)); + fse.writeFileSync(__configdir + "/users.ini", ini.encode(config)); return {success: true} } @@ -254,7 +254,7 @@ function deleteUser(username) { function createNewUserMode(name, template) { source_script = null; if ((template === "template/base") || (template === "") || (template == null)) { - source_script = __basedir + "/NeoRuntime/special/template_base/"; + source_script = __appdir + "/NeoRuntime/special/template_base/"; } else { source_script = neoModules.neoRuntimeManager.getModePath(template); } @@ -310,7 +310,7 @@ module.exports = (_neoModules) => { }, strip: { get: () => { - let c = getFullConfig(`${__datadir}/config/strip.ini`, addSetters=false); + let c = getFullConfig(`${__configdir}/strip.ini`, addSetters=false); c.DEFAULT.matrix = JSON.parse(c.DEFAULT.matrix); c.DEFAULT.segments = c.DEFAULT.segments.split(" "); return c.DEFAULT; @@ -318,13 +318,13 @@ module.exports = (_neoModules) => { set: (c) => { c.segments = c.segments.join(" "); c.matrix = JSON.stringify(c.matrix); - return saveConfig(`${__datadir}/config/strip.ini`, {DEFAULT: c}, removeSetters=false); + return saveConfig(`${__configdir}/strip.ini`, {DEFAULT: c}, removeSetters=false); }, }, - config: getFullConfig(`${__datadir}/config/config.ini`), + config: getFullConfig(`${__configdir}/config.ini`), mode: { create: createNewUserMode, delete: deleteUserMode } } -}; \ No newline at end of file +}; -- cgit v1.2.3 From fd56c70ed709d770410b8f7de49dd18db5b3537e Mon Sep 17 00:00:00 2001 From: Jakob Stendahl Date: Wed, 6 Oct 2021 17:20:54 +0200 Subject: :hammer: Fix new path definition for dev env --- app.js | 2 +- runDev.js | 2 +- src/NeoRuntimeManager/IPC.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/app.js b/app.js index 30146ea..715108b 100644 --- a/app.js +++ b/app.js @@ -13,7 +13,7 @@ if ((process.argv.length >= 3) && (process.argv[2] == "dev")) { global.__datadir = __dirname + "/tmp/userdata"; global.__logdir = __dirname + "/tmp/logs"; } -if (!fse.existsSync(global.__datadir)) { +if (!fse.existsSync(global.__appdir)) { console.log(`CRITICAL UserDir not found '${userDir}'! Exiting...`); process.exit(1); } diff --git a/runDev.js b/runDev.js index 6fbd702..4fafa29 100644 --- a/runDev.js +++ b/runDev.js @@ -12,7 +12,7 @@ Tail = require('tail').Tail; */ webpackLaunchCommand = ["npm", "run", "dev:frontend"]; -nodejsLaunchCommand = ["node", "app.js", `"${__dirname}/tmp"`]; +nodejsLaunchCommand = ["node", "app.js", `dev`]; mkdocsLaunchCommand = ["mkdocs", "build"]; nodejsFileWatcherPaths = [ diff --git a/src/NeoRuntimeManager/IPC.js b/src/NeoRuntimeManager/IPC.js index 817c049..d50d335 100644 --- a/src/NeoRuntimeManager/IPC.js +++ b/src/NeoRuntimeManager/IPC.js @@ -6,7 +6,7 @@ */ const net = require("net"); -let logger = require(__basedir + "/src/logger"); +let logger = require(__appdir + "/src/logger"); /** @type {int} How long wait between each reconnection attempt */ const RECONNECT_INTERVAL = 1000; -- cgit v1.2.3 From d962cdaa317b384b2e82d0f9dc5b9d15a5733869 Mon Sep 17 00:00:00 2001 From: Jakob Stendahl Date: Wed, 6 Oct 2021 17:24:43 +0200 Subject: :children_crossing: Add update button to ui --- src/SocketIO/index.js | 2 ++ src_frontend/Components/Settings/Version.svelte | 6 ++++++ 2 files changed, 8 insertions(+) (limited to 'src') diff --git a/src/SocketIO/index.js b/src/SocketIO/index.js index 2a625ed..1803845 100644 --- a/src/SocketIO/index.js +++ b/src/SocketIO/index.js @@ -194,6 +194,8 @@ function createAuthorizedNamespace(io) { fn({success: true}); }); socket.on("system:update_version", () => { + let p = exec('luxcena-neo-cli.sh update'); + p.unref(); }); /* SSLCert */ diff --git a/src_frontend/Components/Settings/Version.svelte b/src_frontend/Components/Settings/Version.svelte index 29d04ae..35c8f96 100644 --- a/src_frontend/Components/Settings/Version.svelte +++ b/src_frontend/Components/Settings/Version.svelte @@ -20,6 +20,11 @@ }); }); } + + let updateVersionPromise; + function doUpdate() { + authorizedSocket.emit("system:update_version"); + } onMount(async() => { authorizedSocket.emit("version:branch"); @@ -53,6 +58,7 @@

    Current branch

    {#if newVer != version}

    Version available.

    + Update luxcena-neo {/if}
    Check for updates -- cgit v1.2.3 From fab520ff90e3fb5ca877cf3ab8b3ced847ec30fb Mon Sep 17 00:00:00 2001 From: Jakob Stendahl Date: Wed, 6 Oct 2021 17:25:41 +0200 Subject: :art: Only allocate a buffer of the size needed, instead of always 128 bit --- src/NeoRuntimeManager/IPC.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/NeoRuntimeManager/IPC.js b/src/NeoRuntimeManager/IPC.js index d50d335..281a633 100644 --- a/src/NeoRuntimeManager/IPC.js +++ b/src/NeoRuntimeManager/IPC.js @@ -121,27 +121,33 @@ class IPC { * for such events. */ sendCommand(commandType, name, value) { if (this.connected) { - let buf = Buffer.allocUnsafe(128); // It's fine, we know what we are doing - // let buf = Buffer.alloc(128); + let buf; switch (commandType) { case (COMMAND.SET_GLOB): + buf = Buffer.allocUnsafe(3); buf[1] = name; buf[2] = value; break; + case (COMMAND.SET_VAR): if (name.length > 32) { return {success: false, reason: "name too long", detail: "max size of name is 32 bytes"}; } - if (name.length > 93) { return {success: false, reason: "value too long", detail: "max size of value is 93 bytes"}; } + if (value.length > 93) { return {success: false, reason: "value too long", detail: "max size of value is 93 bytes"}; } + buf = Buffer.allocUnsafe(3 + name.length + value.length); buf[1] = name.length; buf[2] = value.length; buf.write(name, 3, name.length, "ascii"); buf.write(value, 3+name.length, value.length, "ascii"); break; + case (COMMAND.SET_SEND_STRIP_BUF): + buf = Buffer.allocUnsafe(2); buf[1] = (name) ? 1 : 0; + break; + default: - logger.info(`IPC UNKNOWN COMMANDTYPE ${commandType}`) - return; + logger.warning(`IPC UNKNOWN COMMANDTYPE ${commandType}`) + return {success: false, reason: "ipc command unknown", detail: commandType}; } buf[0] = commandType; -- cgit v1.2.3 From a49bdd2d135e0ef6ab61898ea655a547184fe0d9 Mon Sep 17 00:00:00 2001 From: Jakob Stendahl <14180120+JakobST1n@users.noreply.github.com> Date: Sat, 9 Oct 2021 11:58:05 +0200 Subject: :pencil2: Fix typo --- src/NeoRuntimeManager/IPC.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/NeoRuntimeManager/IPC.js b/src/NeoRuntimeManager/IPC.js index 281a633..79ce7ea 100644 --- a/src/NeoRuntimeManager/IPC.js +++ b/src/NeoRuntimeManager/IPC.js @@ -6,7 +6,7 @@ */ const net = require("net"); -let logger = require(__appdir + "/src/logger"); +let logger = require(__appdir + "/src/Logger"); /** @type {int} How long wait between each reconnection attempt */ const RECONNECT_INTERVAL = 1000; @@ -181,4 +181,4 @@ function forEachDiff(dict1, dict2, callback) { } } -module.exports = {IPC, COMMAND, GLOBVAR}; \ No newline at end of file +module.exports = {IPC, COMMAND, GLOBVAR}; -- cgit v1.2.3 From 5cf342cd4400d4f555732f80bfdf3639965c4f88 Mon Sep 17 00:00:00 2001 From: Jakob Stendahl <14180120+JakobST1n@users.noreply.github.com> Date: Sat, 9 Oct 2021 22:36:50 +0200 Subject: Update RuntimeProcess.js --- src/NeoRuntimeManager/RuntimeProcess.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/NeoRuntimeManager/RuntimeProcess.js b/src/NeoRuntimeManager/RuntimeProcess.js index 562bb0e..24614fa 100644 --- a/src/NeoRuntimeManager/RuntimeProcess.js +++ b/src/NeoRuntimeManager/RuntimeProcess.js @@ -27,7 +27,7 @@ class RuntimeProcess { } this.isRunning = true; this.proc = spawn.spawn( - "python3", + `${__appdir}/NeoRuntime/Runtime/venv/bin/python`, [ "-u", // This makes us able to get real-time output `${__appdir}/NeoRuntime/Runtime/neo_runtime.py`, -- cgit v1.2.3 From 72ad29efeb4709572e789a57aa94d00a0eaeb97d Mon Sep 17 00:00:00 2001 From: jakobst1n Date: Sun, 10 Oct 2021 20:33:48 +0000 Subject: :construction: Make python 3.5 compatible and fix some weird bugs --- NeoRuntime/Runtime/luxcena_neo/color_utils.py | 7 +++-- NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py | 20 +++++++------- NeoRuntime/Runtime/luxcena_neo/strip.py | 36 ++++++++++++++----------- NeoRuntime/Runtime/neo_runtime.py | 35 ++++++++++++------------ src/NeoRuntimeManager/RuntimeProcess.js | 1 + 5 files changed, 52 insertions(+), 47 deletions(-) (limited to 'src') diff --git a/NeoRuntime/Runtime/luxcena_neo/color_utils.py b/NeoRuntime/Runtime/luxcena_neo/color_utils.py index ab29092..3b7ece4 100644 --- a/NeoRuntime/Runtime/luxcena_neo/color_utils.py +++ b/NeoRuntime/Runtime/luxcena_neo/color_utils.py @@ -31,10 +31,9 @@ def twentyfour_bit_from_rgb(red, green, blue, white=0): def twentyfour_bit_from_hex(hex_color: str): """ Convert the provided hex code to a 24-bit color value. """ + print(hex_color) value = hex_color.lstrip('#') - lv = len(value) - rgb = tuple(int(value[i:i+lv//3], 16) for i in range(0, lv, lv//3)) - return twentyfour_bit_from_rgb(red=rgb[1], green=rgb[0], blue=rgb[2]) + return (int(value[0:2], 16) << 16) | (int(value[2:4], 16) << 8) | (int(value[4:6], 16)) def detect_format_convert_color(*color) -> int: @@ -79,4 +78,4 @@ class Color: def __invert__(self): rgb_color = self.rgb - return Color((255-rgb_color[0], 255-rgb_color[1], 255-rgb_color[2])) \ No newline at end of file + return Color((255-rgb_color[0], 255-rgb_color[1], 255-rgb_color[2])) diff --git a/NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py b/NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py index 5c89ca0..ce0fb62 100644 --- a/NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py +++ b/NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py @@ -61,7 +61,7 @@ class Variables: def __init__(self, package_path): self.__vars = {} - self.__vars_save_file = f"{package_path}/state.json" + self.__vars_save_file = "{}/state.json".format(package_path) self.__saved_variables = {} self.read_saved_variables() @@ -109,7 +109,7 @@ class Variables: def declare(self, variable): """ Declare a new variable. """ if variable.name in self.__vars: - raise Exception(f"Variable with name {variable.name} already defined.") + raise Exception("Variable with name {} already defined.".format(variable.name)) if variable.name in self.__saved_variables: variable.value = self.__saved_variables[variable.name] @@ -164,7 +164,7 @@ class Variable: return {"name": self.name, "value": self.value, "type": self.var_type} def __str__(self): - return f"{self.name}: {self.value}" + return "{}: {}".format(self.name, self.value) def set_save_func(self, save_func): self.__save_func = save_func @@ -173,13 +173,13 @@ class ColorVariable(Variable): def __init__(self, name: str, *color, **kwargs): if not self.verify_color(*color): - raise Exception(f"Invalid color {color}") + raise Exception("Invalid color {}".format(color)) super().__init__(name, self.extract_interesting(*color), VariableType.COLOR, **kwargs) @Variable.value.setter def value(self, *color): if not self.verify_color(*color): - print(f"Attempting to set {self.name} to invalid value {color}") + print("Attempting to set {} to invalid value {}".format(self.name, color)) return super(ColorVariable, type(self)).value.fset(self, self.extract_interesting(*color)) @@ -218,9 +218,9 @@ class IntegerVariable(Variable): if (self.__min <= value <= self.__max): super(ColorVariable, type(self)).value.fset(self, value) else: - print(f"Attempted to set {self.name} to {value} but range is [{self.__min},{self.__max}].") + print("Attempted to set {} to {} but range is [{},{}].".format(self.name, value, self.__min, self.__max)) except ValueError: - print(f"Attempted to set {self.name} to \"{value}\", which is not a valid integer...") + print("Attempted to set {} to \"{}\", which is not a valid integer...".format(self.name, value)) def to_dict(self): return {"name": self.name, "value": self.value, "type": self.var_type, "min": self.__min, "max": self.__max} @@ -240,9 +240,9 @@ class FloatVariable(Variable): if (self.__min <= value <= self.__max): super(ColorVariable, type(self)).value.fset(self, value) else: - print(f"Attempted to set {self.name} to {value} but range is [{self.__min},{self.__max}].") + print("Attempted to set {} to {} but range is [{},{}].".format(self.name, value, self.__min, self.__max)) except ValueError: - print(f"Attempted to set {self.name} to \"{value}\", which is not a valid float...") + print("Attempted to set {} to \"{}\", which is not a valid float...".format(self.name, self.value)) def __str__(self): return round(self.value, 2) @@ -262,4 +262,4 @@ class BooleanVariable(Variable): try: value = bool(value) except: - print(f"Attempted to set {self.name} to \"{value}\", which is not a valid bool...") + print("Attempted to set {} to \"{}\", which is not a valid bool...".format(self.name, value)) diff --git a/NeoRuntime/Runtime/luxcena_neo/strip.py b/NeoRuntime/Runtime/luxcena_neo/strip.py index bfe2bbc..32380da 100644 --- a/NeoRuntime/Runtime/luxcena_neo/strip.py +++ b/NeoRuntime/Runtime/luxcena_neo/strip.py @@ -1,6 +1,6 @@ import json from os import path -from .neopixel import * +import rpi_ws281x as ws from .matrix import Matrix, get_segment_range from .power_calc import calcCurrent @@ -10,11 +10,11 @@ class Strip: def __init__(self, strip_conf): self.SEGMENTS = strip_conf["segments"] - self.LED_FREQ_HZ = strip_conf["led_freq_hz"] # LED signal frequency in hertz (usually 800khz) - self.LED_CHANNEL = strip_conf["led_channel"] # Set to '1' for GPIOs 13, 19, 41, 45, 53 + self.LED_FREQ_HZ = int(strip_conf["led_freq_hz"]) # LED signal frequency in hertz (usually 800khz) + self.LED_CHANNEL = int(strip_conf["led_channel"]) # Set to '1' for GPIOs 13, 19, 41, 45, 53 self.LED_INVERT = strip_conf["led_invert"] # True to invert the signal, (when using NPN transistor level shift) - self.LED_PIN = strip_conf["led_pin"] # 18 uses PWM, 10 uses SPI /dev/spidev0.0 - self.LED_DMA = strip_conf["led_dma"] # DMA channel for generating the signal, on the newer ones, try 10 + self.LED_PIN = int(strip_conf["led_pin"]) # 18 uses PWM, 10 uses SPI /dev/spidev0.0 + self.LED_DMA = int(strip_conf["led_dma"]) # DMA channel for generating the signal, on the newer ones, try 10 self.LED_COUNT = sum(self.SEGMENTS) # Number of LEDs in strip if ("color_calibration" in strip_conf) and (strip_conf["color_calibration"] != ""): @@ -26,15 +26,16 @@ class Strip: self.COLORSTATE = [0 for x in range(self.LED_COUNT)] self.LED_BRIGHTNESS = 255 - - self.strip = Adafruit_NeoPixel( + + self.strip = ws.Adafruit_NeoPixel( self.LED_COUNT, self.LED_PIN, self.LED_FREQ_HZ, self.LED_DMA, self.LED_INVERT, self.LED_BRIGHTNESS, - self.LED_CHANNEL + self.LED_CHANNEL, + strip_type=ws.WS2812_STRIP ) self.strip.begin() @@ -86,15 +87,17 @@ class Strip: self.__power_on = value if (self.power_on): self.__actual_brightness = self.__brightness - # self.strip.setBrightness(self.__brightness) + self.strip.setBrightness(self.__brightness) + self.strip.show() else: self.__actual_brightness = 0 - # self.strip.setBrightness(0) + self.strip.setBrightness(0) + self.strip.show() self.save_globvars() @property def brightness(self): - # return self.strip.getBrightness() + #return self.strip.getBrightness() return self.__actual_brightness @brightness.setter @@ -103,10 +106,11 @@ class Strip: self.__brightness = value if (self.power_on): self.__actual_brightness = value - # self.strip.setBrightness(value) + self.strip.setBrightness(value) + self.strip.show() self.save_globvars() else: - raise Exception(f"Value ({value}) outside allowed range (0-255)") + raise Exception("Value ({}) outside allowed range (0-255)".format(value)) def show(self): """Update the display with the data from the LED buffer.""" @@ -118,7 +122,7 @@ class Strip: """ c = detect_format_convert_color(*color) self.TMPCOLORSTATE[n] = c - # self.strip.setPixelColor(n, ) + self.strip.setPixelColor(n, c) def set_pixel_color_XY(self, x, y, *color): """Set LED at position n to the provided 24-bit color value (in RGB order). @@ -161,7 +165,7 @@ def color_from_hex(hex_color: str): value = hex_color.lstrip('#') lv = len(value) rgb = tuple(int(value[i:i+lv//3], 16) for i in range(0, lv, lv//3)) - return color_from_rgb(red=rgb[1], green=rgb[0], blue=rgb[2]) + return color_from_rgb(red=rgb[0], green=rgb[1], blue=rgb[2]) def detect_format_convert_color(*color) -> int: @@ -181,4 +185,4 @@ def detect_format_convert_color(*color) -> int: return color[0] if (len(color) == 3): return color_from_rgb(*color) - raise ValueError("Invalid parameters provided, check documentation.") \ No newline at end of file + raise ValueError("Invalid parameters provided, check documentation.") diff --git a/NeoRuntime/Runtime/neo_runtime.py b/NeoRuntime/Runtime/neo_runtime.py index 6f84763..b028530 100644 --- a/NeoRuntime/Runtime/neo_runtime.py +++ b/NeoRuntime/Runtime/neo_runtime.py @@ -23,13 +23,13 @@ def init_strip(strip_config_file): strip_config_obj = configparser.ConfigParser() strip_config_obj.read(args.strip_config) strip_config = dict(strip_config_obj.items("DEFAULT")) - strip_config["matrix"] = json.loads(strip_config["matrix"].replace('"', "")) - strip_config["segments"] = [int(x) for x in strip_config["segments"].split(" ")] - strip_config["led_channel"] = int(strip_config["led_channel"]) - strip_config["led_dma"] = int(strip_config["led_dma"]) - strip_config["led_freq_hz"] = int(strip_config["led_freq_hz"]) - strip_config["led_invert"] = (strip_config["led_invert"] == "false") - strip_config["led_pin"] = int(strip_config["led_pin"]) + strip_config["matrix"] = json.loads(strip_config_obj.get("DEFAULT", "matrix").replace('"', "")) + strip_config["segments"] = [int(x) for x in strip_config_obj.get("DEFAULT", "segments").split(" ")] + strip_config["led_channel"] = strip_config_obj.getint("DEFAULT", "led_channel") + strip_config["led_dma"] = strip_config_obj.getint("DEFAULT", "led_dma") + strip_config["led_freq_hz"] = strip_config_obj.getint("DEFAULT", "led_freq_hz") + strip_config["led_invert"] = strip_config_obj.getboolean("DEFAULT", "led_invert") + strip_config["led_pin"] = strip_config_obj.getint("DEFAULT", "led_pin") strip = Strip(strip_config) return strip @@ -38,11 +38,12 @@ def init_package(package_path, entry_module, strip): print ("> Initializing package (mode)...") sys.path.append(package_path) module = importlib.import_module(entry_module) - module_entry_instance = module.Main(package_path) # Make the strip instance available in our modules setattr(module, "strip", strip) + module_entry_instance = module.Main(package_path) + return module_entry_instance def exec_module(module_executor_loop_func): @@ -164,14 +165,14 @@ class NeoRuntime: elif command[1] == 1: self.__strip.brightness = command[2] else: - print(f"Unknown globvar {command[1]}.") + print("Unknown globvar {}.".format(command[1])) elif command[0] == 1: name = command[3:3+command[1]].decode("ascii") value = command[3+command[1]:3+command[1]+command[2]].decode("ascii") if name in self.__module_entry_instance.var: self.__module_entry_instance.var[name] = value else: - print(f"Unknown variable {name}") + print("Unknown variable ".format(name)) elif command[0] == 2: self.__send_strip_buffer = (command[1] == 1) else: @@ -245,19 +246,19 @@ if __name__ == "__main__": args.mode_entry = args.mode_entry.replace("\"", "") args.socket_file = args.socket_file.replace("\"", "") if not path.exists(args.strip_config): - print(f"Strip config not found ({args.strip_config}).") + print("Strip config not found ({})".format(args.strip_config)) sys.exit(1) if not path.exists(args.mode_path): - print(f"Mode path not found ({args.mode_path}).") + print("Mode path not found ({})".format(args.mode_path)) sys.exit(1) - if not path.exists(f"{args.mode_path}/{args.mode_entry}.py"): - print(f"Mode entry not found in mode path ({args.mode_path}/{args.mode_entry}).") + if not path.exists("{}/{}.py".format(args.mode_path, args.mode_entry)): + print("Mode entry not found in mode path ({}/{})".format(args.mode_path, args.mode_entry)) sys.exit(1) - print(f"StripConfig: {args.strip_config}") - print(f"Module : {args.mode_path}/{args.mode_entry}") + print("StripConfig: ".format(args.strip_config)) + print("Module : ".format(args.mode_path, args.mode_entry)) - print(f"> Starting \"{args.mode_path}\" in NeoRuntime.") + print("> Starting \"{}\" in NeoRuntime.".format(args.mode_path)) runtime = NeoRuntime(args.mode_path, args.mode_entry, args.strip_config, args.socket_file) runtime.start() print ("> NeoRuntime exited...") diff --git a/src/NeoRuntimeManager/RuntimeProcess.js b/src/NeoRuntimeManager/RuntimeProcess.js index 24614fa..c5c4749 100644 --- a/src/NeoRuntimeManager/RuntimeProcess.js +++ b/src/NeoRuntimeManager/RuntimeProcess.js @@ -28,6 +28,7 @@ class RuntimeProcess { this.isRunning = true; this.proc = spawn.spawn( `${__appdir}/NeoRuntime/Runtime/venv/bin/python`, + //"python", [ "-u", // This makes us able to get real-time output `${__appdir}/NeoRuntime/Runtime/neo_runtime.py`, -- cgit v1.2.3