initial commit
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
node_modules/
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
docs/
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 leukipp
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 159 KiB |
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 8.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
@@ -0,0 +1,35 @@
|
|||||||
|
const integration = require("./src/integration");
|
||||||
|
const hardware = require("./src/hardware");
|
||||||
|
const webview = require("./src/webview");
|
||||||
|
const { app } = require("electron");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This promise resolves when the app has finished initializing,
|
||||||
|
* allowing to safely create browser windows and perform other
|
||||||
|
* initialization tasks.
|
||||||
|
*/
|
||||||
|
app.whenReady().then(() => {
|
||||||
|
const args = parseArgs(process);
|
||||||
|
webview.init(args) && hardware.init(args) && integration.init(args);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses command-line arguments from the given process object.
|
||||||
|
*
|
||||||
|
* @param {Object} proc - The process object.
|
||||||
|
* @returns {Object} An object mapping argument names to their corresponding values.
|
||||||
|
*/
|
||||||
|
const parseArgs = (proc) => {
|
||||||
|
let argv = proc.argv.slice(1).join(" ").trim();
|
||||||
|
if (argv.length && argv[0] == ".") {
|
||||||
|
argv = argv.slice(1);
|
||||||
|
}
|
||||||
|
argv = argv.replace(/--|=/g, " ").replace(/\s+/g, " ");
|
||||||
|
argv = argv.trim().split(" ");
|
||||||
|
argv = argv.reduce(
|
||||||
|
(args, k, i, v) =>
|
||||||
|
i % 2 ? args : { ...args, [k.replace("-", "_")]: v[i + 1] },
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
return argv;
|
||||||
|
};
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"name": "touchkio",
|
||||||
|
"description": "Home Assistant Touch Kiosk",
|
||||||
|
"author": "leukipp",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "MIT",
|
||||||
|
"main": "main.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "electron . --web-url=https://demo.home-assistant.io",
|
||||||
|
"build": "electron-builder --linux AppImage --arm64"
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"productName": "touchkio",
|
||||||
|
"linux": {
|
||||||
|
"target": [
|
||||||
|
{
|
||||||
|
"target": "AppImage",
|
||||||
|
"arch": [
|
||||||
|
"arm64"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"icon": "img/icon.png",
|
||||||
|
"category": "Network"
|
||||||
|
},
|
||||||
|
"artifactName": "${productName}-${version}-${arch}.AppImage"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"electron": "^33.2.1",
|
||||||
|
"electron-builder": "^25.1.8"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"mqtt": "^5.10.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
+355
@@ -0,0 +1,355 @@
|
|||||||
|
const os = require("os");
|
||||||
|
const fs = require("fs");
|
||||||
|
const process = require("child_process");
|
||||||
|
|
||||||
|
global.HARDWARE = global.HARDWARE || {
|
||||||
|
initialized: false,
|
||||||
|
status: "invalid",
|
||||||
|
display: {
|
||||||
|
status: null,
|
||||||
|
brightness: null,
|
||||||
|
notifiers: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the hardware with the provided arguments.
|
||||||
|
*
|
||||||
|
* @param {Object} args - The command-line arguments to customize the initialization process.
|
||||||
|
* @returns {boolean} Returns true if the initialization was successful.
|
||||||
|
*/
|
||||||
|
const init = (args) => {
|
||||||
|
const model = getModel();
|
||||||
|
const valid = model && model.includes("Raspberry Pi 5");
|
||||||
|
|
||||||
|
// Check supported hardware
|
||||||
|
if (valid) {
|
||||||
|
console.log("\nModel:", getModel());
|
||||||
|
console.log("Serial Number:", getSerialNumber());
|
||||||
|
console.log("Host Name:", getHostName());
|
||||||
|
console.log("Up Time:", getUpTime());
|
||||||
|
console.log("Memory Size:", getMemorySize());
|
||||||
|
console.log("Memory Usage:", getMemoryUsage());
|
||||||
|
console.log("Processor Usage:", getProcessorUsage());
|
||||||
|
console.log("Processor Temperature:", getProcessorTemperature());
|
||||||
|
console.log("Display Status:", getDisplayStatus());
|
||||||
|
console.log("Display Brightness:", getDisplayBrightness(), "\n");
|
||||||
|
|
||||||
|
// Init globals
|
||||||
|
HARDWARE.initialized = true;
|
||||||
|
HARDWARE.status = "valid";
|
||||||
|
|
||||||
|
// Check for display changes
|
||||||
|
setInterval(update, 500);
|
||||||
|
} else {
|
||||||
|
console.warn(`Device ${model} not supported`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return valid;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the shared hardware properties.
|
||||||
|
*/
|
||||||
|
const update = () => {
|
||||||
|
if (!HARDWARE.initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const path = "/sys/class/backlight/10-0045";
|
||||||
|
|
||||||
|
// Display status has changed
|
||||||
|
const status = fs.readFileSync(`${path}/bl_power`, "utf8").trim();
|
||||||
|
if (status !== HARDWARE.display.status) {
|
||||||
|
console.log("Update Display Status:", getDisplayStatus());
|
||||||
|
HARDWARE.display.status = status;
|
||||||
|
HARDWARE.display.notifiers.forEach((notifier) => {
|
||||||
|
notifier();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display brightness has changed
|
||||||
|
const brightness = fs.readFileSync(`${path}/brightness`, "utf8").trim();
|
||||||
|
if (brightness !== HARDWARE.display.brightness) {
|
||||||
|
console.log("Update Display Brightness:", getDisplayBrightness());
|
||||||
|
HARDWARE.display.brightness = brightness;
|
||||||
|
HARDWARE.display.notifiers.forEach((notifier) => {
|
||||||
|
notifier();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the Raspberry Pi model name using `/sys/firmware/devicetree/base/model`.
|
||||||
|
*
|
||||||
|
* @returns {string|null} The model of the Raspberry Pi or null if an error occurs.
|
||||||
|
*/
|
||||||
|
const getModel = () => {
|
||||||
|
return execSyncCommand("cat", ["/sys/firmware/devicetree/base/model"]);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the Raspberry Pi serial number using `/sys/firmware/devicetree/base/serial-number`.
|
||||||
|
*
|
||||||
|
* @returns {string|null} The serial number of the Raspberry Pi or null if an error occurs.
|
||||||
|
*/
|
||||||
|
const getSerialNumber = () => {
|
||||||
|
return execSyncCommand("cat", [
|
||||||
|
"/sys/firmware/devicetree/base/serial-number",
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the host name of the current system using `os.hostname()`.
|
||||||
|
*
|
||||||
|
* @returns {string} The host name of the system.
|
||||||
|
*/
|
||||||
|
const getHostName = () => {
|
||||||
|
return os.hostname();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the up time of the system in minutes using `os.uptime()`.
|
||||||
|
*
|
||||||
|
* @returns {number} The up time of the system in minutes.
|
||||||
|
*/
|
||||||
|
const getUpTime = () => {
|
||||||
|
return os.uptime() / 60;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the total available memory in gibibytes using `os.totalmem()`.
|
||||||
|
*
|
||||||
|
* @returns {number} The total available memory in GiB.
|
||||||
|
*/
|
||||||
|
const getMemorySize = () => {
|
||||||
|
return os.totalmem() / 1024 ** 3;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current memory usage as a percentage using `os.totalmem()` and `os.freemem()`.
|
||||||
|
*
|
||||||
|
* @returns {number} The percentage of used memory.
|
||||||
|
*/
|
||||||
|
const getMemoryUsage = () => {
|
||||||
|
return ((os.totalmem() - os.freemem()) / os.totalmem()) * 100;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the CPU load average over the last 5 minutes as a percentage using `os.loadavg()` and `os.cpus()`.
|
||||||
|
*
|
||||||
|
* @returns {number} The CPU load average percentage over the last 5 minutes.
|
||||||
|
*/
|
||||||
|
const getProcessorUsage = () => {
|
||||||
|
return (os.loadavg()[1] / os.cpus().length) * 100;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current CPU temperature using `vcgencmd measure_temp`.
|
||||||
|
*
|
||||||
|
* @returns {number|null} The CPU temperature in degrees Celsius or null if an error occurs.
|
||||||
|
*/
|
||||||
|
const getProcessorTemperature = () => {
|
||||||
|
const output = execSyncCommand("vcgencmd", ["measure_temp"]);
|
||||||
|
if (output !== null) {
|
||||||
|
const temp = output.match(/temp=(\d+\.\d+)/);
|
||||||
|
if (temp && temp[1]) {
|
||||||
|
return parseFloat(temp[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current display power status using `wlopm`.
|
||||||
|
*
|
||||||
|
* @returns {string|null} The output of the command or null if an error occurs.
|
||||||
|
*/
|
||||||
|
const getDisplayStatus = () => {
|
||||||
|
const status = execSyncCommand("wlopm", []);
|
||||||
|
if (status !== null) {
|
||||||
|
return status.slice(status.lastIndexOf(" ") + 1).toUpperCase();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
|
||||||
|
/* This also works, but it's better to use `wlopm` for getter and setter methods.
|
||||||
|
const status = execSyncCommand("cat", [
|
||||||
|
"/sys/class/backlight/10-0045/bl_power",
|
||||||
|
]);
|
||||||
|
if (status !== null) {
|
||||||
|
return status === "0" ? "ON" : "OFF";
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the display power status using `wlopm`.
|
||||||
|
*
|
||||||
|
* This function takes a desired status ('ON' or 'OFF') and executes
|
||||||
|
* the appropriate command to set the display status.
|
||||||
|
*
|
||||||
|
* @param {string} status - The desired status ('ON' or 'OFF').
|
||||||
|
* @param {function} callback - A callback function that receives the output or error.
|
||||||
|
*/
|
||||||
|
const setDisplayStatus = (status, callback = null) => {
|
||||||
|
if (status !== "ON" && status !== "OFF") {
|
||||||
|
console.error("Status must be 'ON' or 'OFF'.");
|
||||||
|
if (typeof callback === "function") callback(null, "Invalid status.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const args = status === "ON" ? ["--on", "*"] : ["--off", "*"];
|
||||||
|
execAsyncCommand("wlopm", args, callback);
|
||||||
|
|
||||||
|
/* This also works, but interferes with the built-in screen blanking function.
|
||||||
|
const value = status === "ON" ? "0" : "4";
|
||||||
|
const proc = execAsyncCommand(
|
||||||
|
"tee",
|
||||||
|
["/sys/class/backlight/10-0045/bl_power"],
|
||||||
|
callback
|
||||||
|
);
|
||||||
|
proc.stdin.write(value.toString());
|
||||||
|
proc.stdin.end();
|
||||||
|
*/
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current display brightness level using `/sys/class/backlight/10-0045/brightness`.
|
||||||
|
*
|
||||||
|
* This function retrieves the brightness level, which is in the range of 0-31,
|
||||||
|
* and maps it to a percentage in the range of 0-100 percent.
|
||||||
|
*
|
||||||
|
* @returns {number|null} The brightness level as a percentage or null if an error occurs.
|
||||||
|
*/
|
||||||
|
const getDisplayBrightness = () => {
|
||||||
|
const brightness = execSyncCommand("cat", [
|
||||||
|
"/sys/class/backlight/10-0045/brightness",
|
||||||
|
]);
|
||||||
|
if (brightness !== null) {
|
||||||
|
return Math.round((parseInt(brightness, 10) / 31) * 100);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the display brightness level using `/sys/class/backlight/10-0045/brightness`.
|
||||||
|
*
|
||||||
|
* This function takes a brightness value in the range of 1-100 percent,
|
||||||
|
* maps it to the range of 0-31, and writes it to the system.
|
||||||
|
*
|
||||||
|
* @param {number} brightness - The desired brightness level (1-100).
|
||||||
|
* @param {function} callback - A callback function that receives the output or error.
|
||||||
|
*/
|
||||||
|
const setDisplayBrightness = (brightness, callback = null) => {
|
||||||
|
if (typeof brightness !== "number" || brightness < 1 || brightness > 100) {
|
||||||
|
console.error("Brightness must be a number between 1 and 100.");
|
||||||
|
if (typeof callback === "function") callback(null, "Invalid brightness.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const value = Math.max(1, Math.min(Math.round((brightness / 100) * 31), 31));
|
||||||
|
const proc = execAsyncCommand(
|
||||||
|
"tee",
|
||||||
|
["/sys/class/backlight/10-0045/brightness"],
|
||||||
|
callback
|
||||||
|
);
|
||||||
|
proc.stdin.write(value.toString());
|
||||||
|
proc.stdin.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shuts down the system using `sudo shutdown -h now`.
|
||||||
|
*
|
||||||
|
* This function executes the command asynchronously.
|
||||||
|
* The output of the command will be provided through the callback function.
|
||||||
|
*
|
||||||
|
* @param {function} callback - A callback function that receives the output or error.
|
||||||
|
*/
|
||||||
|
const shutdownSystem = (callback = null) => {
|
||||||
|
execAsyncCommand("sudo", ["shutdown", "-h", "now"], callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reboots the system using `sudo reboot`.
|
||||||
|
*
|
||||||
|
* This function executes the command asynchronously.
|
||||||
|
* The output of the command will be provided through the callback function.
|
||||||
|
*
|
||||||
|
* @param {function} callback - A callback function that receives the output or error.
|
||||||
|
*/
|
||||||
|
const rebootSystem = (callback = null) => {
|
||||||
|
execAsyncCommand("sudo", ["reboot"], callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes a command synchronously and returns the output.
|
||||||
|
*
|
||||||
|
* @param {string} cmd - The command to execute.
|
||||||
|
* @param {Array<string>} args - The arguments for the command.
|
||||||
|
* @returns {string|null} The output of the command or null if an error occurs.
|
||||||
|
*/
|
||||||
|
const execSyncCommand = (cmd, args = []) => {
|
||||||
|
try {
|
||||||
|
const output = process.execSync([cmd, ...args].join(" "), {
|
||||||
|
encoding: "utf8",
|
||||||
|
});
|
||||||
|
return output.trim().replace(/\0/g, "");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Execute Sync:", error.message);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes a command asynchronously.
|
||||||
|
*
|
||||||
|
* @param {string} cmd - The command to execute.
|
||||||
|
* @param {Array<string>} args - The arguments for the command.
|
||||||
|
* @param {function} callback - A callback function that receives the output or error.
|
||||||
|
* @returns {object} The spawned process object.
|
||||||
|
*/
|
||||||
|
const execAsyncCommand = (cmd, args = [], callback = null) => {
|
||||||
|
let errorOutput = "";
|
||||||
|
let successOutput = "";
|
||||||
|
let proc = process.spawn(cmd, args);
|
||||||
|
proc.stderr.on("data", (data) => {
|
||||||
|
if (data) {
|
||||||
|
errorOutput += data.toString();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
proc.stdout.on("data", (data) => {
|
||||||
|
if (data) {
|
||||||
|
successOutput += data.toString();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
proc.on("close", (code) => {
|
||||||
|
try {
|
||||||
|
if (typeof callback === "function") {
|
||||||
|
if (code === 0) {
|
||||||
|
callback(successOutput.trim().replace(/\0/g, ""), null);
|
||||||
|
} else {
|
||||||
|
callback(null, errorOutput.trim().replace(/\0/g, ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Execute Async:", error.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return proc;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
init,
|
||||||
|
update,
|
||||||
|
getModel,
|
||||||
|
getSerialNumber,
|
||||||
|
getHostName,
|
||||||
|
getUpTime,
|
||||||
|
getMemorySize,
|
||||||
|
getMemoryUsage,
|
||||||
|
getProcessorUsage,
|
||||||
|
getProcessorTemperature,
|
||||||
|
getDisplayStatus,
|
||||||
|
setDisplayStatus,
|
||||||
|
getDisplayBrightness,
|
||||||
|
setDisplayBrightness,
|
||||||
|
shutdownSystem,
|
||||||
|
rebootSystem,
|
||||||
|
};
|
||||||
@@ -0,0 +1,677 @@
|
|||||||
|
const mqtt = require("mqtt");
|
||||||
|
const hardware = require("./hardware");
|
||||||
|
const { app } = require("electron");
|
||||||
|
|
||||||
|
global.INTEGRATION = global.INTEGRATION || {
|
||||||
|
initialized: false,
|
||||||
|
status: "offline",
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the integration with the provided arguments.
|
||||||
|
*
|
||||||
|
* @param {Object} args - The command-line arguments to customize the initialization process.
|
||||||
|
* @returns {boolean} Returns true if the initialization was successful.
|
||||||
|
*/
|
||||||
|
const init = (args) => {
|
||||||
|
if (!args.mqtt_url) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const url = new URL(args.mqtt_url);
|
||||||
|
const user = args.mqtt_user ? args.mqtt_user : null;
|
||||||
|
const password = args.mqtt_password ? args.mqtt_password : null;
|
||||||
|
const discovery = args.mqtt_discovery ? args.mqtt_discovery : "homeassistant";
|
||||||
|
|
||||||
|
const model = hardware.getModel();
|
||||||
|
const hostName = hardware.getHostName();
|
||||||
|
const serialNumber = hardware.getSerialNumber();
|
||||||
|
|
||||||
|
const deviceId = serialNumber.slice(-6).toUpperCase();
|
||||||
|
const deviceName = hostName.charAt(0).toUpperCase() + hostName.slice(1);
|
||||||
|
|
||||||
|
// Init globals
|
||||||
|
INTEGRATION.node = `rpi_${deviceId}`;
|
||||||
|
INTEGRATION.discovery = discovery;
|
||||||
|
INTEGRATION.device = {
|
||||||
|
name: `Raspberry Pi ${deviceName}`,
|
||||||
|
model: model,
|
||||||
|
serial_number: serialNumber,
|
||||||
|
manufacturer: "Raspberry Pi Ltd",
|
||||||
|
identifiers: [INTEGRATION.node],
|
||||||
|
sw_version: `${app.getName()}-v${app.getVersion()}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Connection settings
|
||||||
|
const masked = password === null ? "null" : "*".repeat(password.length);
|
||||||
|
const options =
|
||||||
|
user === null || password == null
|
||||||
|
? null
|
||||||
|
: {
|
||||||
|
username: user,
|
||||||
|
password: password,
|
||||||
|
};
|
||||||
|
console.log("MQTT Connecting:", `${user}:${masked}@${url.toString()}`);
|
||||||
|
INTEGRATION.client = mqtt.connect(url.toString(), options);
|
||||||
|
|
||||||
|
// Client connected
|
||||||
|
INTEGRATION.client
|
||||||
|
.on("connect", () => {
|
||||||
|
console.log(`MQTT Connected: ${url.toString()}\n`);
|
||||||
|
|
||||||
|
// Init client controls
|
||||||
|
initShutdown(INTEGRATION.client);
|
||||||
|
initReboot(INTEGRATION.client);
|
||||||
|
initRefresh(INTEGRATION.client);
|
||||||
|
initKiosk(INTEGRATION.client);
|
||||||
|
initDisplay(INTEGRATION.client);
|
||||||
|
|
||||||
|
// Init client sensors
|
||||||
|
initModel(INTEGRATION.client);
|
||||||
|
initSerialNumber(INTEGRATION.client);
|
||||||
|
initHostName(INTEGRATION.client);
|
||||||
|
initUpTime(INTEGRATION.client);
|
||||||
|
initMemorySize(INTEGRATION.client);
|
||||||
|
initMemoryUsage(INTEGRATION.client);
|
||||||
|
initProcessorUsage(INTEGRATION.client);
|
||||||
|
initProcessorTemperature(INTEGRATION.client);
|
||||||
|
initHeartbeat(INTEGRATION.client);
|
||||||
|
|
||||||
|
// Integration initialized
|
||||||
|
INTEGRATION.initialized = true;
|
||||||
|
INTEGRATION.status = "online";
|
||||||
|
})
|
||||||
|
.on("error", (error) => {
|
||||||
|
console.error("MQTT:", error);
|
||||||
|
INTEGRATION.status = "offline";
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update sensor states on display change
|
||||||
|
HARDWARE.display.notifiers.push(() => {
|
||||||
|
updateDisplay(INTEGRATION.client);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update heartbeat time periodically
|
||||||
|
setInterval(() => {
|
||||||
|
updateHeartbeat(INTEGRATION.client);
|
||||||
|
}, 30 * 1000);
|
||||||
|
|
||||||
|
// Update sensor states periodically
|
||||||
|
setInterval(update, 60 * 1000);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the shared integration properties.
|
||||||
|
*/
|
||||||
|
const update = () => {
|
||||||
|
if (!INTEGRATION.initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update client sensors
|
||||||
|
updateKiosk(INTEGRATION.client);
|
||||||
|
updateUpTime(INTEGRATION.client);
|
||||||
|
updateMemoryUsage(INTEGRATION.client);
|
||||||
|
updateProcessorUsage(INTEGRATION.client);
|
||||||
|
updateProcessorTemperature(INTEGRATION.client);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the shutdown button and handles the execute logic.
|
||||||
|
*
|
||||||
|
* @param {Object} client - Instance of the mqtt client.
|
||||||
|
*/
|
||||||
|
const initShutdown = (client) => {
|
||||||
|
const root = `${INTEGRATION.discovery}/button/${INTEGRATION.node}/shutdown`;
|
||||||
|
client
|
||||||
|
.publish(
|
||||||
|
`${root}/config`,
|
||||||
|
JSON.stringify({
|
||||||
|
name: "Shutdown",
|
||||||
|
unique_id: `${INTEGRATION.node}_shutdown`,
|
||||||
|
command_topic: `${root}/execute`,
|
||||||
|
icon: "mdi:power",
|
||||||
|
device: INTEGRATION.device,
|
||||||
|
}),
|
||||||
|
{ qos: 1, retain: true }
|
||||||
|
)
|
||||||
|
.on("message", (topic, message) => {
|
||||||
|
if (topic === `${root}/execute`) {
|
||||||
|
console.log("Shutdown system...");
|
||||||
|
hardware.setDisplayStatus("ON");
|
||||||
|
hardware.shutdownSystem();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.subscribe(`${root}/execute`);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the reboot button and handles the execute logic.
|
||||||
|
*
|
||||||
|
* @param {Object} client - Instance of the mqtt client.
|
||||||
|
*/
|
||||||
|
const initReboot = (client) => {
|
||||||
|
const root = `${INTEGRATION.discovery}/button/${INTEGRATION.node}/reboot`;
|
||||||
|
client
|
||||||
|
.publish(
|
||||||
|
`${root}/config`,
|
||||||
|
JSON.stringify({
|
||||||
|
name: "Reboot",
|
||||||
|
unique_id: `${INTEGRATION.node}_reboot`,
|
||||||
|
command_topic: `${root}/execute`,
|
||||||
|
icon: "mdi:restart",
|
||||||
|
device: INTEGRATION.device,
|
||||||
|
}),
|
||||||
|
{ qos: 1, retain: true }
|
||||||
|
)
|
||||||
|
.on("message", (topic, message) => {
|
||||||
|
if (topic === `${root}/execute`) {
|
||||||
|
console.log("Rebooting system...");
|
||||||
|
hardware.setDisplayStatus("ON");
|
||||||
|
hardware.rebootSystem();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.subscribe(`${root}/execute`);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the refresh button and handles the execute logic.
|
||||||
|
*
|
||||||
|
* @param {Object} client - Instance of the mqtt client.
|
||||||
|
*/
|
||||||
|
const initRefresh = (client) => {
|
||||||
|
const root = `${INTEGRATION.discovery}/button/${INTEGRATION.node}/refresh`;
|
||||||
|
client
|
||||||
|
.publish(
|
||||||
|
`${root}/config`,
|
||||||
|
JSON.stringify({
|
||||||
|
name: "Refresh",
|
||||||
|
unique_id: `${INTEGRATION.node}_refresh`,
|
||||||
|
command_topic: `${root}/execute`,
|
||||||
|
icon: "mdi:web-refresh",
|
||||||
|
device: INTEGRATION.device,
|
||||||
|
}),
|
||||||
|
{ qos: 1, retain: true }
|
||||||
|
)
|
||||||
|
.on("message", (topic, message) => {
|
||||||
|
if (topic === `${root}/execute`) {
|
||||||
|
console.log("Refresh webview...");
|
||||||
|
hardware.setDisplayStatus("ON");
|
||||||
|
WEBVIEW.view.webContents.reloadIgnoringCache();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.subscribe(`${root}/execute`);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the kiosk select status and handles the execute logic.
|
||||||
|
*
|
||||||
|
* @param {Object} client - Instance of the mqtt client.
|
||||||
|
*/
|
||||||
|
const initKiosk = (client) => {
|
||||||
|
const root = `${INTEGRATION.discovery}/select/${INTEGRATION.node}/kiosk`;
|
||||||
|
client
|
||||||
|
.publish(
|
||||||
|
`${root}/config`,
|
||||||
|
JSON.stringify({
|
||||||
|
name: `Kiosk`,
|
||||||
|
unique_id: `${INTEGRATION.node}_kiosk`,
|
||||||
|
command_topic: `${root}/set`,
|
||||||
|
state_topic: `${root}/status`,
|
||||||
|
value_template: "{{ value }}",
|
||||||
|
options: [
|
||||||
|
"Framed",
|
||||||
|
"Fullscreen",
|
||||||
|
"Maximized",
|
||||||
|
"Minimized",
|
||||||
|
"Terminated",
|
||||||
|
],
|
||||||
|
icon: "mdi:overscan",
|
||||||
|
device: INTEGRATION.device,
|
||||||
|
}),
|
||||||
|
{ qos: 1, retain: true }
|
||||||
|
)
|
||||||
|
.on("message", (topic, message) => {
|
||||||
|
if (topic === `${root}/set`) {
|
||||||
|
const status = message.toString();
|
||||||
|
console.log("Set Kiosk Status:", status);
|
||||||
|
hardware.setDisplayStatus("ON");
|
||||||
|
switch (status) {
|
||||||
|
case "Framed":
|
||||||
|
WEBVIEW.window.restore();
|
||||||
|
WEBVIEW.window.unmaximize();
|
||||||
|
WEBVIEW.window.setFullScreen(false);
|
||||||
|
break;
|
||||||
|
case "Fullscreen":
|
||||||
|
WEBVIEW.window.restore();
|
||||||
|
WEBVIEW.window.unmaximize();
|
||||||
|
WEBVIEW.window.setFullScreen(true);
|
||||||
|
break;
|
||||||
|
case "Maximized":
|
||||||
|
WEBVIEW.window.restore();
|
||||||
|
WEBVIEW.window.setFullScreen(false);
|
||||||
|
WEBVIEW.window.maximize();
|
||||||
|
break;
|
||||||
|
case "Minimized":
|
||||||
|
WEBVIEW.window.restore();
|
||||||
|
WEBVIEW.window.setFullScreen(false);
|
||||||
|
WEBVIEW.window.minimize();
|
||||||
|
break;
|
||||||
|
case "Terminated":
|
||||||
|
app.quit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.subscribe(`${root}/set`);
|
||||||
|
updateKiosk(client);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the kiosk status via the mqtt connection.
|
||||||
|
*
|
||||||
|
* @param {Object} client - Instance of the mqtt client.
|
||||||
|
*/
|
||||||
|
const updateKiosk = (client) => {
|
||||||
|
const kiosk = WEBVIEW.status;
|
||||||
|
const root = `${INTEGRATION.discovery}/select/${INTEGRATION.node}/kiosk`;
|
||||||
|
client.publish(`${root}/status`, `${kiosk}`, {
|
||||||
|
qos: 1,
|
||||||
|
retain: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the display status, brightness and handles the execute logic.
|
||||||
|
*
|
||||||
|
* @param {Object} client - Instance of the mqtt client.
|
||||||
|
*/
|
||||||
|
const initDisplay = (client) => {
|
||||||
|
const root = `${INTEGRATION.discovery}/light/${INTEGRATION.node}/display`;
|
||||||
|
client
|
||||||
|
.publish(
|
||||||
|
`${root}/config`,
|
||||||
|
JSON.stringify({
|
||||||
|
name: `Display`,
|
||||||
|
unique_id: `${INTEGRATION.node}_display`,
|
||||||
|
command_topic: `${root}/set`,
|
||||||
|
state_topic: `${root}/status`,
|
||||||
|
brightness_command_topic: `${root}/brightness/set`,
|
||||||
|
brightness_state_topic: `${root}/brightness/status`,
|
||||||
|
brightness_scale: 100,
|
||||||
|
icon: "mdi:monitor-shimmer",
|
||||||
|
platform: "light",
|
||||||
|
device: INTEGRATION.device,
|
||||||
|
}),
|
||||||
|
{ qos: 1, retain: true }
|
||||||
|
)
|
||||||
|
.on("message", (topic, message) => {
|
||||||
|
if (topic === `${root}/set`) {
|
||||||
|
const status = message.toString();
|
||||||
|
console.log("Set Display Status:", status);
|
||||||
|
hardware.setDisplayStatus(status);
|
||||||
|
} else if (topic === `${root}/brightness/set`) {
|
||||||
|
const brightness = parseInt(message, 10);
|
||||||
|
console.log("Set Display Brightness:", brightness);
|
||||||
|
hardware.setDisplayBrightness(brightness);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.subscribe(`${root}/set`)
|
||||||
|
.subscribe(`${root}/brightness/set`);
|
||||||
|
updateDisplay(client);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the display status, brightness via the mqtt connection.
|
||||||
|
*
|
||||||
|
* @param {Object} client - Instance of the mqtt client.
|
||||||
|
*/
|
||||||
|
const updateDisplay = (client) => {
|
||||||
|
const status = hardware.getDisplayStatus();
|
||||||
|
const brightness = hardware.getDisplayBrightness();
|
||||||
|
const root = `${INTEGRATION.discovery}/light/${INTEGRATION.node}/display`;
|
||||||
|
client.publish(`${root}/status`, `${status}`, {
|
||||||
|
qos: 1,
|
||||||
|
retain: true,
|
||||||
|
});
|
||||||
|
client.publish(`${root}/brightness/status`, `${brightness}`, {
|
||||||
|
qos: 1,
|
||||||
|
retain: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the model sensor.
|
||||||
|
*
|
||||||
|
* @param {Object} client - Instance of the mqtt client.
|
||||||
|
*/
|
||||||
|
const initModel = (client) => {
|
||||||
|
const root = `${INTEGRATION.discovery}/sensor/${INTEGRATION.node}/model`;
|
||||||
|
client.publish(
|
||||||
|
`${root}/config`,
|
||||||
|
JSON.stringify({
|
||||||
|
name: `Model`,
|
||||||
|
unique_id: `${INTEGRATION.node}_model`,
|
||||||
|
state_topic: `${root}/status`,
|
||||||
|
value_template: "{{ value }}",
|
||||||
|
icon: "mdi:raspberry-pi",
|
||||||
|
device: INTEGRATION.device,
|
||||||
|
}),
|
||||||
|
{ qos: 1, retain: true }
|
||||||
|
);
|
||||||
|
updateModel(client);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the model sensor via the mqtt connection.
|
||||||
|
*
|
||||||
|
* @param {Object} client - Instance of the mqtt client.
|
||||||
|
*/
|
||||||
|
const updateModel = (client) => {
|
||||||
|
const model = hardware.getModel();
|
||||||
|
const root = `${INTEGRATION.discovery}/sensor/${INTEGRATION.node}/model`;
|
||||||
|
client.publish(`${root}/status`, `${model}`, {
|
||||||
|
qos: 1,
|
||||||
|
retain: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the serial number sensor.
|
||||||
|
*
|
||||||
|
* @param {Object} client - Instance of the mqtt client.
|
||||||
|
*/
|
||||||
|
const initSerialNumber = (client) => {
|
||||||
|
const root = `${INTEGRATION.discovery}/sensor/${INTEGRATION.node}/serial_number`;
|
||||||
|
client.publish(
|
||||||
|
`${root}/config`,
|
||||||
|
JSON.stringify({
|
||||||
|
name: `Serial Number`,
|
||||||
|
unique_id: `${INTEGRATION.node}_serial_number`,
|
||||||
|
state_topic: `${root}/status`,
|
||||||
|
value_template: "{{ value }}",
|
||||||
|
icon: "mdi:hexadecimal",
|
||||||
|
device: INTEGRATION.device,
|
||||||
|
}),
|
||||||
|
{ qos: 1, retain: true }
|
||||||
|
);
|
||||||
|
updateSerialNumber(client);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the serial number sensor via the mqtt connection.
|
||||||
|
*
|
||||||
|
* @param {Object} client - Instance of the mqtt client.
|
||||||
|
*/
|
||||||
|
const updateSerialNumber = (client) => {
|
||||||
|
const serialNumber = hardware.getSerialNumber();
|
||||||
|
const root = `${INTEGRATION.discovery}/sensor/${INTEGRATION.node}/serial_number`;
|
||||||
|
client.publish(`${root}/status`, `${serialNumber}`, {
|
||||||
|
qos: 1,
|
||||||
|
retain: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the host name sensor.
|
||||||
|
*
|
||||||
|
* @param {Object} client - Instance of the mqtt client.
|
||||||
|
*/
|
||||||
|
const initHostName = (client) => {
|
||||||
|
const root = `${INTEGRATION.discovery}/sensor/${INTEGRATION.node}/host_name`;
|
||||||
|
client.publish(
|
||||||
|
`${root}/config`,
|
||||||
|
JSON.stringify({
|
||||||
|
name: `Host Name`,
|
||||||
|
unique_id: `${INTEGRATION.node}_host_name`,
|
||||||
|
state_topic: `${root}/status`,
|
||||||
|
value_template: "{{ value }}",
|
||||||
|
icon: "mdi:console-network",
|
||||||
|
device: INTEGRATION.device,
|
||||||
|
}),
|
||||||
|
{ qos: 1, retain: true }
|
||||||
|
);
|
||||||
|
updateHostName(client);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the host name sensor via the mqtt connection.
|
||||||
|
*
|
||||||
|
* @param {Object} client - Instance of the mqtt client.
|
||||||
|
*/
|
||||||
|
const updateHostName = (client) => {
|
||||||
|
const hostName = hardware.getHostName();
|
||||||
|
const root = `${INTEGRATION.discovery}/sensor/${INTEGRATION.node}/host_name`;
|
||||||
|
client.publish(`${root}/status`, `${hostName}`, {
|
||||||
|
qos: 1,
|
||||||
|
retain: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the up time sensor.
|
||||||
|
*
|
||||||
|
* @param {Object} client - Instance of the mqtt client.
|
||||||
|
*/
|
||||||
|
const initUpTime = (client) => {
|
||||||
|
const root = `${INTEGRATION.discovery}/sensor/${INTEGRATION.node}/up_time`;
|
||||||
|
client.publish(
|
||||||
|
`${root}/config`,
|
||||||
|
JSON.stringify({
|
||||||
|
name: `Up Time`,
|
||||||
|
unique_id: `${INTEGRATION.node}_up_time`,
|
||||||
|
state_topic: `${root}/status`,
|
||||||
|
value_template: "{{ (value | float) | round(0) }}",
|
||||||
|
unit_of_measurement: "min",
|
||||||
|
icon: "mdi:timeline-clock",
|
||||||
|
device: INTEGRATION.device,
|
||||||
|
}),
|
||||||
|
{ qos: 1, retain: true }
|
||||||
|
);
|
||||||
|
updateUpTime(client);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the up time sensor via the mqtt connection.
|
||||||
|
*
|
||||||
|
* @param {Object} client - Instance of the mqtt client.
|
||||||
|
*/
|
||||||
|
const updateUpTime = (client) => {
|
||||||
|
const upTime = hardware.getUpTime();
|
||||||
|
const root = `${INTEGRATION.discovery}/sensor/${INTEGRATION.node}/up_time`;
|
||||||
|
client.publish(`${root}/status`, `${upTime}`, {
|
||||||
|
qos: 1,
|
||||||
|
retain: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the memory size sensor.
|
||||||
|
*
|
||||||
|
* @param {Object} client - Instance of the mqtt client.
|
||||||
|
*/
|
||||||
|
const initMemorySize = (client) => {
|
||||||
|
const root = `${INTEGRATION.discovery}/sensor/${INTEGRATION.node}/memory_size`;
|
||||||
|
client.publish(
|
||||||
|
`${root}/config`,
|
||||||
|
JSON.stringify({
|
||||||
|
name: `Memory Size`,
|
||||||
|
unique_id: `${INTEGRATION.node}_memory_size`,
|
||||||
|
state_topic: `${root}/status`,
|
||||||
|
value_template: "{{ (value | float) | round(2) }}",
|
||||||
|
unit_of_measurement: "GiB",
|
||||||
|
icon: "mdi:memory",
|
||||||
|
device: INTEGRATION.device,
|
||||||
|
}),
|
||||||
|
{ qos: 1, retain: true }
|
||||||
|
);
|
||||||
|
updateMemorySize(client);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the memory size sensor via the mqtt connection.
|
||||||
|
*
|
||||||
|
* @param {Object} client - Instance of the mqtt client.
|
||||||
|
*/
|
||||||
|
const updateMemorySize = (client) => {
|
||||||
|
const memorySize = hardware.getMemorySize();
|
||||||
|
const root = `${INTEGRATION.discovery}/sensor/${INTEGRATION.node}/memory_size`;
|
||||||
|
client.publish(`${root}/status`, `${memorySize}`, {
|
||||||
|
qos: 1,
|
||||||
|
retain: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the memory usage sensor.
|
||||||
|
*
|
||||||
|
* @param {Object} client - Instance of the mqtt client.
|
||||||
|
*/
|
||||||
|
const initMemoryUsage = (client) => {
|
||||||
|
const root = `${INTEGRATION.discovery}/sensor/${INTEGRATION.node}/memory_usage`;
|
||||||
|
client.publish(
|
||||||
|
`${root}/config`,
|
||||||
|
JSON.stringify({
|
||||||
|
name: `Memory Usage`,
|
||||||
|
unique_id: `${INTEGRATION.node}_memory_usage`,
|
||||||
|
state_topic: `${root}/status`,
|
||||||
|
value_template: "{{ (value | float) | round(0) }}",
|
||||||
|
unit_of_measurement: "%",
|
||||||
|
icon: "mdi:memory-arrow-down",
|
||||||
|
device: INTEGRATION.device,
|
||||||
|
}),
|
||||||
|
{ qos: 1, retain: true }
|
||||||
|
);
|
||||||
|
updateMemoryUsage(client);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the memory usage sensor via the mqtt connection.
|
||||||
|
*
|
||||||
|
* @param {Object} client - Instance of the mqtt client.
|
||||||
|
*/
|
||||||
|
const updateMemoryUsage = (client) => {
|
||||||
|
const memoryUsage = hardware.getMemoryUsage();
|
||||||
|
const root = `${INTEGRATION.discovery}/sensor/${INTEGRATION.node}/memory_usage`;
|
||||||
|
client.publish(`${root}/status`, `${memoryUsage}`, {
|
||||||
|
qos: 1,
|
||||||
|
retain: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the processor usage sensor.
|
||||||
|
*
|
||||||
|
* @param {Object} client - Instance of the mqtt client.
|
||||||
|
*/
|
||||||
|
const initProcessorUsage = (client) => {
|
||||||
|
const root = `${INTEGRATION.discovery}/sensor/${INTEGRATION.node}/processor_usage`;
|
||||||
|
client.publish(
|
||||||
|
`${root}/config`,
|
||||||
|
JSON.stringify({
|
||||||
|
name: `Processor Usage`,
|
||||||
|
unique_id: `${INTEGRATION.node}_processor_usage`,
|
||||||
|
state_topic: `${root}/status`,
|
||||||
|
value_template: "{{ (value | float) | round(0) }}",
|
||||||
|
unit_of_measurement: "%",
|
||||||
|
icon: "mdi:cpu-64-bit",
|
||||||
|
device: INTEGRATION.device,
|
||||||
|
}),
|
||||||
|
{ qos: 1, retain: true }
|
||||||
|
);
|
||||||
|
updateProcessorUsage(client);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the processor usage sensor via the mqtt connection.
|
||||||
|
*
|
||||||
|
* @param {Object} client - Instance of the mqtt client.
|
||||||
|
*/
|
||||||
|
const updateProcessorUsage = (client) => {
|
||||||
|
const processorUsage = hardware.getProcessorUsage();
|
||||||
|
const root = `${INTEGRATION.discovery}/sensor/${INTEGRATION.node}/processor_usage`;
|
||||||
|
client.publish(`${root}/status`, `${processorUsage}`, {
|
||||||
|
qos: 1,
|
||||||
|
retain: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the processor temperature sensor.
|
||||||
|
*
|
||||||
|
* @param {Object} client - Instance of the mqtt client.
|
||||||
|
*/
|
||||||
|
const initProcessorTemperature = (client) => {
|
||||||
|
const root = `${INTEGRATION.discovery}/sensor/${INTEGRATION.node}/processor_temperature`;
|
||||||
|
client.publish(
|
||||||
|
`${root}/config`,
|
||||||
|
JSON.stringify({
|
||||||
|
name: `Processor Temperature`,
|
||||||
|
unique_id: `${INTEGRATION.node}_processor_temperature`,
|
||||||
|
state_topic: `${root}/status`,
|
||||||
|
value_template: "{{ (value | float) | round(0) }}",
|
||||||
|
unit_of_measurement: "°C",
|
||||||
|
icon: "mdi:radiator",
|
||||||
|
device: INTEGRATION.device,
|
||||||
|
}),
|
||||||
|
{ qos: 1, retain: true }
|
||||||
|
);
|
||||||
|
updateProcessorTemperature(client);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the processor temperature sensor via the mqtt connection.
|
||||||
|
*
|
||||||
|
* @param {Object} client - Instance of the mqtt client.
|
||||||
|
*/
|
||||||
|
const updateProcessorTemperature = (client) => {
|
||||||
|
const processorTemperature = hardware.getProcessorTemperature();
|
||||||
|
const root = `${INTEGRATION.discovery}/sensor/${INTEGRATION.node}/processor_temperature`;
|
||||||
|
client.publish(`${root}/status`, `${processorTemperature}`, {
|
||||||
|
qos: 1,
|
||||||
|
retain: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the heartbeat sensor.
|
||||||
|
*
|
||||||
|
* @param {Object} client - Instance of the mqtt client.
|
||||||
|
*/
|
||||||
|
const initHeartbeat = (client) => {
|
||||||
|
const root = `${INTEGRATION.discovery}/sensor/${INTEGRATION.node}/heartbeat`;
|
||||||
|
client.publish(
|
||||||
|
`${root}/config`,
|
||||||
|
JSON.stringify({
|
||||||
|
name: `Heartbeat`,
|
||||||
|
unique_id: `${INTEGRATION.node}_heartbeat`,
|
||||||
|
state_topic: `${root}/status`,
|
||||||
|
value_template: "{{ value }}",
|
||||||
|
icon: "mdi:heart-flash",
|
||||||
|
device: INTEGRATION.device,
|
||||||
|
}),
|
||||||
|
{ qos: 1, retain: true }
|
||||||
|
);
|
||||||
|
updateHeartbeat(client);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the heartbeat sensor via the mqtt connection.
|
||||||
|
*
|
||||||
|
* @param {Object} client - Instance of the mqtt client.
|
||||||
|
*/
|
||||||
|
const updateHeartbeat = (client) => {
|
||||||
|
const now = new Date();
|
||||||
|
const local = new Date(now.getTime() - now.getTimezoneOffset() * 60 * 1000);
|
||||||
|
const heartbeat = local.toISOString().replace(/\.\d{3}Z$/, "");
|
||||||
|
const root = `${INTEGRATION.discovery}/sensor/${INTEGRATION.node}/heartbeat`;
|
||||||
|
client.publish(`${root}/status`, `${heartbeat}`, {
|
||||||
|
qos: 1,
|
||||||
|
retain: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
init,
|
||||||
|
update,
|
||||||
|
};
|
||||||
+157
@@ -0,0 +1,157 @@
|
|||||||
|
const path = require("path");
|
||||||
|
const hardware = require("./hardware");
|
||||||
|
const integration = require("./integration");
|
||||||
|
const { app, nativeTheme, BaseWindow, WebContentsView } = require("electron");
|
||||||
|
|
||||||
|
global.WEBVIEW = global.WEBVIEW || {
|
||||||
|
initialized: false,
|
||||||
|
status: "offline",
|
||||||
|
locked: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the webview with the provided arguments.
|
||||||
|
*
|
||||||
|
* @param {Object} args - The command-line arguments to customize the initialization process.
|
||||||
|
* @returns {boolean} Returns true if the initialization was successful.
|
||||||
|
*/
|
||||||
|
const init = (args) => {
|
||||||
|
if (!args.web_url) {
|
||||||
|
console.error("Please provide the '--web-url' parameter.");
|
||||||
|
return app.quit();
|
||||||
|
}
|
||||||
|
const url = new URL(args.web_url);
|
||||||
|
const theme = args.web_theme ? args.web_theme : "dark";
|
||||||
|
const zoom = args.web_zoom ? parseFloat(args.web_zoom) : 1.25;
|
||||||
|
|
||||||
|
// Init global root window
|
||||||
|
WEBVIEW.window = new BaseWindow({
|
||||||
|
icon: path.join(__dirname, "..", "img", "icon.png"),
|
||||||
|
});
|
||||||
|
WEBVIEW.window.setMenuBarVisibility(false);
|
||||||
|
WEBVIEW.window.setFullScreen(true);
|
||||||
|
nativeTheme.themeSource = theme;
|
||||||
|
// WEBVIEW.window.minimize();
|
||||||
|
|
||||||
|
// Init global main webview
|
||||||
|
WEBVIEW.view = new WebContentsView();
|
||||||
|
WEBVIEW.window.contentView.addChildView(WEBVIEW.view);
|
||||||
|
WEBVIEW.view.webContents.loadURL(url.toString());
|
||||||
|
|
||||||
|
// Register events
|
||||||
|
windowEvents(WEBVIEW.window, zoom);
|
||||||
|
viewEvents(WEBVIEW.view);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the shared webview properties.
|
||||||
|
*/
|
||||||
|
const update = () => {
|
||||||
|
if (!WEBVIEW.initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update window status
|
||||||
|
if (WEBVIEW.window.isFullScreen()) {
|
||||||
|
WEBVIEW.status = "Fullscreen";
|
||||||
|
} else if (WEBVIEW.window.isMinimized()) {
|
||||||
|
WEBVIEW.status = "Minimized";
|
||||||
|
} else if (WEBVIEW.window.isMaximized()) {
|
||||||
|
WEBVIEW.status = "Maximized";
|
||||||
|
} else {
|
||||||
|
WEBVIEW.status = "Framed";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update integration sensor
|
||||||
|
console.log("Update Kiosk Status:", WEBVIEW.status);
|
||||||
|
integration.update();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register window events and handler.
|
||||||
|
*
|
||||||
|
* @param {Object} window - The root window object.
|
||||||
|
* @param {number} zoom - Zoom level used for the webview.
|
||||||
|
*/
|
||||||
|
const windowEvents = (window, zoom) => {
|
||||||
|
// Handle window resize for webview bounds
|
||||||
|
const resize = () => {
|
||||||
|
const { width, height } = window.getBounds();
|
||||||
|
WEBVIEW.view.setBounds({ x: 0, y: 0, width: width, height: height });
|
||||||
|
WEBVIEW.view.webContents.setZoomFactor(zoom);
|
||||||
|
};
|
||||||
|
window.on("ready-to-show", resize);
|
||||||
|
window.on("resize", resize);
|
||||||
|
resize();
|
||||||
|
|
||||||
|
// Handle focus change for webview lock
|
||||||
|
HARDWARE.display.notifiers.push(() => {
|
||||||
|
WEBVIEW.locked = hardware.getDisplayStatus() === "OFF";
|
||||||
|
if (WEBVIEW.locked) {
|
||||||
|
window.blur();
|
||||||
|
}
|
||||||
|
/* This also works, but it doesn't allow to react for screen wake up.
|
||||||
|
window.setIgnoreMouseEvents(getDisplayStatus() === "OFF");
|
||||||
|
*/
|
||||||
|
});
|
||||||
|
window.on("focus", () => {
|
||||||
|
if (WEBVIEW.locked) {
|
||||||
|
hardware.setDisplayStatus("ON");
|
||||||
|
window.blur();
|
||||||
|
}
|
||||||
|
WEBVIEW.locked = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update window status
|
||||||
|
window.on("minimize", update);
|
||||||
|
window.on("restore", update);
|
||||||
|
window.on("maximize", update);
|
||||||
|
window.on("unmaximize", update);
|
||||||
|
window.on("enter-full-screen", update);
|
||||||
|
window.on("leave-full-screen", update);
|
||||||
|
process.on("SIGINT", app.quit);
|
||||||
|
app.on("before-quit", () => {
|
||||||
|
WEBVIEW.status = "Terminated";
|
||||||
|
integration.update();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register webview events and handler.
|
||||||
|
*
|
||||||
|
* @param {Object} view - The main webview object.
|
||||||
|
*/
|
||||||
|
const viewEvents = (view) => {
|
||||||
|
// Enable webview touch emulation
|
||||||
|
view.webContents.debugger.attach("1.1");
|
||||||
|
view.webContents.debugger.sendCommand(
|
||||||
|
"Emulation.setEmitTouchEventsForMouse",
|
||||||
|
{
|
||||||
|
enabled: true,
|
||||||
|
configuration: "mobile",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Disable webview hyperlinks
|
||||||
|
view.webContents.setWindowOpenHandler(() => {
|
||||||
|
return { action: "deny" };
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove webview scrollbar
|
||||||
|
view.webContents.on("dom-ready", () => {
|
||||||
|
view.webContents.insertCSS("::-webkit-scrollbar { display: none; }");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Webview fully loaded
|
||||||
|
view.webContents.on("did-finish-load", () => {
|
||||||
|
WEBVIEW.initialized = true;
|
||||||
|
update();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
init,
|
||||||
|
update,
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user