Skip to content

Commit fd746cc

Browse files
committed
feat: additional MQTT controls and sensors #7
1 parent 4f426df commit fd746cc

File tree

3 files changed

+372
-37
lines changed

3 files changed

+372
-37
lines changed

src/hardware.js

+174-11
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ global.HARDWARE = global.HARDWARE || {
1919
},
2020
notifiers: [],
2121
},
22+
keyboard: {
23+
visible: null,
24+
notifiers: [],
25+
},
2226
};
2327

2428
/**
@@ -69,6 +73,23 @@ const init = async (args) => {
6973
console.log(`\nDisplay Status [${HARDWARE.display.status.path}]:`, getDisplayStatus());
7074
console.log(`Display Brightness [${HARDWARE.display.brightness.path}]:`, getDisplayBrightness(), "\n");
7175

76+
// Check for keyboard visibility changes
77+
HARDWARE.support.keyboardVisibility = processRuns("squeekboard");
78+
setKeyboardVisibility("OFF", (reply, error) => {
79+
if (!reply || error) {
80+
return;
81+
}
82+
dbusMonitor("/sm/puri/OSK0", (property, error) => {
83+
if (!property || error) {
84+
return;
85+
}
86+
HARDWARE.keyboard.visibility = property.Visible === "true";
87+
HARDWARE.keyboard.notifiers.forEach((notifier) => {
88+
notifier();
89+
});
90+
});
91+
});
92+
7293
// Check for display changes
7394
setInterval(update, 500);
7495

@@ -124,9 +145,12 @@ const compatibleDevice = () => {
124145
/**
125146
* Queries the session type for the logged in user.
126147
*
127-
* @returns {string} Returns session type x11/wayland or null if an error occurs.
148+
* @returns {string} Returns session type 'x11'/'wayland' or null if an error occurs.
128149
*/
129150
const sessionType = () => {
151+
if (!commandExists("loginctl")) {
152+
return null;
153+
}
130154
return execSyncCommand("loginctl", [
131155
"show-session",
132156
"$(loginctl show-user $(whoami) -p Display --value)",
@@ -146,7 +170,7 @@ const session = (type) => {
146170
/**
147171
* Gets the model name using `/sys/firmware/devicetree/base/model` or `/sys/class/dmi/id/product_name`.
148172
*
149-
* @returns {string|null} The model name of the device or "Generic" if not found.
173+
* @returns {string|null} The model name of the device or 'Generic' if not found.
150174
*/
151175
const getModel = () => {
152176
const paths = ["/sys/firmware/devicetree/base/model", "/sys/class/dmi/id/product_name"];
@@ -161,7 +185,7 @@ const getModel = () => {
161185
/**
162186
* Gets the vendor name using `/sys/class/dmi/id/board_vendor`.
163187
*
164-
* @returns {string|null} The vendor name of the device or "Generic" if not found.
188+
* @returns {string|null} The vendor name of the device or 'Generic' if not found.
165189
*/
166190
const getVendor = () => {
167191
const paths = ["/sys/class/dmi/id/board_vendor"];
@@ -180,7 +204,7 @@ const getVendor = () => {
180204
/**
181205
* Gets the serial number using `/sys/firmware/devicetree/base/serial-number` or `/sys/class/dmi/id/product_serial`.
182206
*
183-
* @returns {string|null} The serial number of the device or "123456" if not found.
207+
* @returns {string|null} The serial number of the device or '123456' if not found.
184208
*/
185209
const getSerialNumber = () => {
186210
const paths = ["/sys/firmware/devicetree/base/serial-number", "/sys/class/dmi/id/product_serial"];
@@ -195,7 +219,7 @@ const getSerialNumber = () => {
195219
/**
196220
* Gets the host machine id using `/etc/machine-id`.
197221
*
198-
* @returns {string} The machine id of the system or "123456" if not found.
222+
* @returns {string} The machine id of the system or '123456' if not found.
199223
*/
200224
const getMachineId = () => {
201225
return execSyncCommand("cat", ["/etc/machine-id"]) || "123456";
@@ -290,7 +314,7 @@ const getDisplayStatusPath = () => {
290314
/**
291315
* Gets the current display power status using `wlopm` or `xset`.
292316
*
293-
* @returns {string|null} The display status ON/OFF or null if an error occurs.
317+
* @returns {string|null} The display status as 'ON'/'OFF' or null if an error occurs.
294318
*/
295319
const getDisplayStatus = () => {
296320
if (!HARDWARE.support.displayStatus) {
@@ -323,6 +347,7 @@ const getDisplayStatus = () => {
323347
*/
324348
const setDisplayStatus = (status, callback = null) => {
325349
if (!HARDWARE.support.displayStatus) {
350+
if (typeof callback === "function") callback(null, "Not supported");
326351
return;
327352
}
328353
if (status !== "ON" && status !== "OFF") {
@@ -398,6 +423,7 @@ const getDisplayBrightness = () => {
398423
*/
399424
const setDisplayBrightness = (brightness, callback = null) => {
400425
if (!HARDWARE.support.displayBrightness) {
426+
if (typeof callback === "function") callback(null, "Not supported");
401427
return;
402428
}
403429
if (typeof brightness !== "number" || brightness < 1 || brightness > 100) {
@@ -412,6 +438,52 @@ const setDisplayBrightness = (brightness, callback = null) => {
412438
proc.stdin.end();
413439
};
414440

441+
/**
442+
* Gets the keyboard visibility using global properties.
443+
*
444+
* @returns {string|null} The keyboard visibility as 'ON'/'OFF' or null if an error occurs.
445+
*/
446+
const getKeyboardVisibility = () => {
447+
if (!HARDWARE.support.keyboardVisibility) {
448+
return null;
449+
}
450+
return HARDWARE.keyboard.visibility ? "ON" : "OFF";
451+
};
452+
453+
/**
454+
* Sets the keyboard visibility using `dbus-send`.
455+
*
456+
* This function takes a desired visibility ('ON' or 'OFF') and executes
457+
* the appropriate command to show or hide the keyboard.
458+
*
459+
* @param {boolean} visibility - The desired visibility ('ON' or 'OFF').
460+
* @param {function} callback - A callback function that receives the output or error.
461+
*/
462+
const setKeyboardVisibility = (visibility, callback = null) => {
463+
if (!HARDWARE.support.keyboardVisibility) {
464+
if (typeof callback === "function") callback(null, "Not supported");
465+
return;
466+
}
467+
const visible = visibility === "ON";
468+
HARDWARE.keyboard.visibility = visible;
469+
dbusCall("/sm/puri/OSK0", "SetVisible", [`boolean:${visible}`], callback);
470+
};
471+
472+
/**
473+
* Checks if system upgrades are available using `apt`.
474+
*
475+
* @returns {Array<string>} A list of package names that are available for upgrade.
476+
*/
477+
const checkPackageUpgrades = () => {
478+
if (!commandExists("apt")) {
479+
return [];
480+
}
481+
const output = execSyncCommand("apt", ["list", "--upgradable", "2>/dev/null"]);
482+
const packages = (output || "").trim().split("\n");
483+
packages.shift();
484+
return packages;
485+
};
486+
415487
/**
416488
* Shuts down the system using `sudo shutdown -h now`.
417489
*
@@ -437,14 +509,27 @@ const rebootSystem = (callback = null) => {
437509
};
438510

439511
/**
440-
* Checks if a command exists using `which`.
512+
* Checks if a process is running using `pidof`.
441513
*
442-
* @param {string} cmd - The command name to check.
443-
* @returns {boolean} Returns true if the command exists.
514+
* @param {string} name - The process name to check.
515+
* @returns {boolean} Returns true if the process runs.
444516
*/
445-
const commandExists = (cmd) => {
517+
const processRuns = (name) => {
446518
try {
447-
return !!process.execSync(`which ${cmd}`, { encoding: "utf8" });
519+
return !!process.execSync(`pidof ${name}`, { encoding: "utf8" });
520+
} catch {}
521+
return false;
522+
};
523+
524+
/**
525+
* Checks if a command is available using `which`.
526+
*
527+
* @param {string} name - The command name to check.
528+
* @returns {boolean} Returns true if the command is available.
529+
*/
530+
const commandExists = (name) => {
531+
try {
532+
return !!process.execSync(`which ${name}`, { encoding: "utf8" });
448533
} catch {}
449534
return false;
450535
};
@@ -499,6 +584,81 @@ const execAsyncCommand = (cmd, args = [], callback = null) => {
499584
}
500585
} catch (error) {
501586
console.error("Execute Async:", error.message);
587+
if (typeof callback === "function") {
588+
callback(null, error.message);
589+
}
590+
}
591+
});
592+
return proc;
593+
};
594+
595+
/**
596+
* Executes a D-Bus method call synchronously using `dbus-send`.
597+
*
598+
* @param {string} path - The D-Bus object path.
599+
* @param {string} method - The D-Bus method name.
600+
* @param {Array<string>} values - The argument values for the D-Bus method.
601+
* @param {function} callback - A callback function that receives the output or error.
602+
*/
603+
const dbusCall = (path, method, values, callback = null) => {
604+
const cmd = "dbus-send";
605+
const iface = path.slice(1).replace(/\//g, ".");
606+
const dest = `${iface} ${path} ${iface}.${method} ${values.join(" ")}`;
607+
const args = ["--print-reply", "--type=method_call", `--dest=${dest}`];
608+
try {
609+
const output = process.execSync([cmd, ...args].join(" "), { encoding: "utf8" });
610+
if (typeof callback === "function") {
611+
callback(output.trim().replace(/\0/g, ""), null);
612+
}
613+
} catch (error) {
614+
console.error("Call D-Bus:", error.message);
615+
if (typeof callback === "function") {
616+
callback(null, error.message);
617+
}
618+
}
619+
};
620+
621+
/**
622+
* Monitors D-Bus property changes asynchronously using `dbus-monitor`.
623+
*
624+
* @param {string} path - The D-Bus object path.
625+
* @param {function} callback - A callback function that receives the changed property.
626+
* @returns {object} The spawned process object.
627+
*/
628+
const dbusMonitor = (path, callback) => {
629+
const cmd = "dbus-monitor";
630+
const args = [`interface='org.freedesktop.DBus.Properties',member='PropertiesChanged',path='${path}'`];
631+
const proc = process.spawn(cmd, args);
632+
proc.stdout.on("data", (data) => {
633+
try {
634+
const signal = data.toString();
635+
if (signal.includes("member=PropertiesChanged") && signal.includes(`path=${path}`)) {
636+
const dicts = [...signal.matchAll(/dict entry\(\s*([^)]*?)\)/g)].map((dict) => dict[1].trim());
637+
if (dicts.length) {
638+
dicts.forEach((dict) => {
639+
const key = dict.match(/string "(.*?)"/);
640+
const value = dict.match(/(?<=variant\s+)(.*)/);
641+
if (key && value && typeof callback === "function") {
642+
callback({ [key[1].trim()]: value[1].trim().split(" ").pop() }, null);
643+
}
644+
});
645+
} else {
646+
callback({ Visible: `${HARDWARE.keyboard.visibility}` }, null);
647+
}
648+
}
649+
} catch (error) {
650+
console.error("Monitor D-Bus:", error.message);
651+
if (typeof callback === "function") {
652+
callback(null, error.message);
653+
}
654+
}
655+
});
656+
proc.stderr.on("data", (data) => {
657+
if (data) {
658+
console.error("Monitor D-Bus:", data.toString());
659+
if (typeof callback === "function") {
660+
callback(null, data.toString());
661+
}
502662
}
503663
});
504664
return proc;
@@ -524,6 +684,9 @@ module.exports = {
524684
getDisplayBrightnessMax,
525685
getDisplayBrightness,
526686
setDisplayBrightness,
687+
getKeyboardVisibility,
688+
setKeyboardVisibility,
689+
checkPackageUpgrades,
527690
shutdownSystem,
528691
rebootSystem,
529692
};

0 commit comments

Comments
 (0)