From 8d924735bd08052f15d6a69e8b19b025e1117a68 Mon Sep 17 00:00:00 2001 From: Sonny Piers Date: Tue, 9 Dec 2025 11:58:24 +0000 Subject: [PATCH 1/6] replace wait-port with promise-port --- lib/helpers.js | 6 +++--- lib/helpers.test.js | 3 ++- lib/nodered.js | 7 +++---- lib/package-lock.json | 40 +++++++++++++--------------------------- lib/package.json | 2 +- 5 files changed, 22 insertions(+), 36 deletions(-) diff --git a/lib/helpers.js b/lib/helpers.js index acf0ee15a..f76569de5 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -1,3 +1,6 @@ +import { opendir } from "fs/promises" +import { join } from "path" + export function createNodeFromAsync(type, fn, input, output) { return function (RED) { function Node(config) { @@ -22,9 +25,6 @@ export function createNodeFromAsync(type, fn, input, output) { } } -import { opendir } from "fs/promises" -import { join } from "path" - export async function* walk(dir) { let fsdir try { diff --git a/lib/helpers.test.js b/lib/helpers.test.js index aaeff038f..fddfeac14 100644 --- a/lib/helpers.test.js +++ b/lib/helpers.test.js @@ -1,7 +1,8 @@ import { test } from "node:test" -import { cache } from "./helpers.js" import { setTimeout } from "node:timers/promises" +import { cache } from "./helpers.js" + test("cached sync", async (t) => { const fn = t.mock.fn(() => "foo") diff --git a/lib/nodered.js b/lib/nodered.js index 8f0bb156c..94956f773 100644 --- a/lib/nodered.js +++ b/lib/nodered.js @@ -1,4 +1,5 @@ -import waitPort from "wait-port" +import { promisePortOpen } from "promise-port" + import { createRequire } from "node:module" const require = createRequire(import.meta.url) @@ -7,7 +8,5 @@ const node_red_settings_path = "/home/pi/PlanktoScope/node-red/settings.cjs" export async function promiseDashboardOnline() { const { uiPort: port } = require(node_red_settings_path) - await waitPort({ - port, - }) + await promisePortOpen(port) } diff --git a/lib/package-lock.json b/lib/package-lock.json index 982d38762..b251d669a 100644 --- a/lib/package-lock.json +++ b/lib/package-lock.json @@ -15,7 +15,7 @@ "mime": "^4.1.0", "mqtt": "^5.14.1", "p-event": "^7.0.0", - "wait-port": "^1.1.0" + "promise-port": "^1.0.1" }, "devDependencies": { "@eslint/js": "^9.38.0", @@ -404,6 +404,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -605,6 +606,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -627,6 +629,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -639,17 +642,9 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, "license": "MIT" }, - "node_modules/commander": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", - "license": "MIT", - "engines": { - "node": "^12.20.0 || >=14" - } - }, "node_modules/commist": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/commist/-/commist-3.2.0.tgz", @@ -1413,6 +1408,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -2236,6 +2232,12 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "license": "MIT" }, + "node_modules/promise-port": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-port/-/promise-port-1.0.1.tgz", + "integrity": "sha512-C3OxFrV+hH+ZTru48dHjI6qRCq+4eTbLVTu80/ig03Nvs703rzQbo+dHrI5tp/cDckSTfPNULottEwVKZje0zQ==", + "license": "ISC" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -2535,6 +2537,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -2688,23 +2691,6 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, - "node_modules/wait-port": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/wait-port/-/wait-port-1.1.0.tgz", - "integrity": "sha512-3e04qkoN3LxTMLakdqeWth8nih8usyg+sf1Bgdf9wwUkp05iuK1eSY/QpLvscT/+F/gA89+LpUmmgBtesbqI2Q==", - "license": "MIT", - "dependencies": { - "chalk": "^4.1.2", - "commander": "^9.3.0", - "debug": "^4.3.4" - }, - "bin": { - "wait-port": "bin/wait-port.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/lib/package.json b/lib/package.json index 971443ea0..f876bca54 100644 --- a/lib/package.json +++ b/lib/package.json @@ -11,7 +11,7 @@ "mime": "^4.1.0", "mqtt": "^5.14.1", "p-event": "^7.0.0", - "wait-port": "^1.1.0" + "promise-port": "^1.0.1" }, "devDependencies": { "@eslint/js": "^9.38.0", From 0351f35a7da410c59fe96667252ad9ba9fb2972f Mon Sep 17 00:00:00 2001 From: Sonny Piers Date: Tue, 9 Dec 2025 11:59:03 +0000 Subject: [PATCH 2/6] first test --- lib/network.test.js | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 lib/network.test.js diff --git a/lib/network.test.js b/lib/network.test.js new file mode 100644 index 000000000..39e37ed6a --- /dev/null +++ b/lib/network.test.js @@ -0,0 +1,35 @@ +import { describe, afterEach, test, before, after } from "node:test" +// import { Systemctl } from "./systemctl.js" +import { getWifis, scan } from "./network.js" + +// let systemctl = null +// const service = "mediamtx" + +// before(async () => { +// systemctl = new Systemctl() +// await systemctl.init() +// }) + +// Prevents "service start request repeated too quickly, refusing to start" +// and the likes - https://serverfault.com/questions/845471/service-start-request-repeated-too-quickly-refusing-to-start-limit +// afterEach(async () => { +// await systemctl.reload() +// }) + +test("scan", async () => { + await scan() +}) + +describe("getWifis", async () => { + test("returns PlanktoScope own wifi", async (t) => { + const wifis = await getWifis() + console.log(wifis) + const wifi = wifis.find((wifi) => wifi.ssid == "PlanktoScope fork-wave") + t.assert.ok(wifi) + }) +}) + +// after(async () => { +// await systemctl.deinit() +// systemctl = null +// }) From c03076c0d8c1eb5376c2c5e4e034ca561794cc50 Mon Sep 17 00:00:00 2001 From: Sonny Piers Date: Tue, 9 Dec 2025 13:26:35 +0000 Subject: [PATCH 3/6] rework for tests --- lib/network.js | 165 +++++++++++++++++++++++++++----------------- lib/network.test.js | 34 ++++----- lib/systemctl.js | 11 ++- 3 files changed, 123 insertions(+), 87 deletions(-) diff --git a/lib/network.js b/lib/network.js index 405e57fb7..662c15859 100644 --- a/lib/network.js +++ b/lib/network.js @@ -4,72 +4,107 @@ import { pEvent } from "p-event" import { getSystemBus } from "./dbus.js" -const service = getSystemBus().getService("org.freedesktop.NetworkManager") - -const NetworkManager = await service.getInterface( - "/org/freedesktop/NetworkManager", - "org.freedesktop.NetworkManager", -) - -const device_path = await NetworkManager.GetDeviceByIpIface("wlan0") - -const [DeviceWireless, DeviceWireless_Properties] = await Promise.all([ - service.getInterface( - device_path, - "org.freedesktop.NetworkManager.Device.Wireless", - ), - service.getInterface(device_path, "org.freedesktop.DBus.Properties"), -]) - -export { DeviceWireless } - -export async function scan() { - await DeviceWireless.RequestScan({}) - - // > To know when the scan is finished, use the "PropertiesChanged" signal from "org.freedesktop.DBus.Properties" to listen to changes to the "LastScan" property. - // https://networkmanager.dev/docs/api/latest/gdbus-org.freedesktop.NetworkManager.Device.Wireless.html#gdbus-method-org-freedesktop-NetworkManager-Device-Wireless.RequestScan - await pEvent(DeviceWireless_Properties, "PropertiesChanged", { - multiArgs: true, - filter: ([ - interface_name, - changed_properties /*invalidated_properties*/, - ]) => { - if (interface_name !== "org.freedesktop.NetworkManager.Device.Wireless") - return false - - const LastScan = changed_properties.find((changed_property) => { - const [property_name] = changed_property - return property_name === "LastScan" - }) - - return !!LastScan - }, - }) -} +export class NetworkManager { + system_bus = null + network_manager = null + service = null + + async init() { + if (this.network_manager) return + + const system_bus = getSystemBus() + const service = getSystemBus().getService("org.freedesktop.NetworkManager") + + const network_manager = await service.getInterface( + "/org/freedesktop/NetworkManager", + "org.freedesktop.NetworkManager", + ) + + const device_path = await network_manager.GetDeviceByIpIface("wlan0") + const [DeviceWireless, DeviceWireless_Properties] = await Promise.all([ + service.getInterface( + device_path, + "org.freedesktop.NetworkManager.Device.Wireless", + ), + service.getInterface(device_path, "org.freedesktop.DBus.Properties"), + ]) + + Object.assign(this, { + system_bus, + service, + network_manager, + device_path, + DeviceWireless, + DeviceWireless_Properties, + }) + } + + // TODO: real async + async deinit() { + this.system_bus?.connection.end() + this.system_bus = null + this.network_manager = null + this.service.bus.connection.end() + this.service = null + } + + async scan() { + await this.DeviceWireless.RequestScan({}) + + // > To know when the scan is finished, use the "PropertiesChanged" signal from "org.freedesktop.DBus.Properties" to listen to changes to the "LastScan" property. + // https://networkmanager.dev/docs/api/latest/gdbus-org.freedesktop.NetworkManager.Device.Wireless.html#gdbus-method-org-freedesktop-NetworkManager-Device-Wireless.RequestScan + await pEvent(this.DeviceWireless_Properties, "PropertiesChanged", { + multiArgs: true, + filter: ([ + interface_name, + changed_properties /*invalidated_properties*/, + ]) => { + if (interface_name !== "org.freedesktop.NetworkManager.Device.Wireless") + return false + + const LastScan = changed_properties.find((changed_property) => { + const [property_name] = changed_property + return property_name === "LastScan" + }) + + return !!LastScan + }, + }) + } + + async getWifis() { + const access_point_paths = await this.DeviceWireless.GetAllAccessPoints() + + const access_points = await Promise.all( + access_point_paths.map((access_point_path) => { + return this.service.getInterface( + access_point_path, + "org.freedesktop.NetworkManager.AccessPoint", + ) + }), + ) + + return Promise.all( + access_points.map(async (access_point) => { + const ssid = new TextDecoder().decode(await access_point.Ssid()) + const frequency = await access_point.Frequency() + const strength = await access_point.Strength() + const path = access_point.$parent.name + return { ssid, frequency, strength, path } + }), + ) + } -export async function getWifis() { - const access_point_paths = await DeviceWireless.GetAllAccessPoints() - - const access_points = await Promise.all( - access_point_paths.map((access_point_path) => { - return service.getInterface( - access_point_path, - "org.freedesktop.NetworkManager.AccessPoint", - ) - }), - ) - - return Promise.all( - access_points.map(async (access_point) => { - const ssid = new TextDecoder().decode(await access_point.Ssid()) - const frequency = await access_point.Frequency() - const strength = await access_point.Strength() - const path = access_point.$parent.name - return { ssid, frequency, strength, path } - }), - ) + async connectToWifi(path) { + await NetworkManager.AddAndActivateConnection([], this.device_path, path) + } } -export async function connectToWifi(path) { - await NetworkManager.AddAndActivateConnection([], device_path, path) +if (import.meta.main) { + const networkmanager = new NetworkManager() + await networkmanager.init() + await networkmanager.scan() + const wifis = await networkmanager.getWifis() + console.log(wifis) + await networkmanager.deinit() } diff --git a/lib/network.test.js b/lib/network.test.js index 39e37ed6a..dedb45c19 100644 --- a/lib/network.test.js +++ b/lib/network.test.js @@ -1,35 +1,27 @@ -import { describe, afterEach, test, before, after } from "node:test" -// import { Systemctl } from "./systemctl.js" -import { getWifis, scan } from "./network.js" +import { describe, test, before, after } from "node:test" +import { NetworkManager } from "./network.js" -// let systemctl = null +let networkmanager = null // const service = "mediamtx" -// before(async () => { -// systemctl = new Systemctl() -// await systemctl.init() -// }) - -// Prevents "service start request repeated too quickly, refusing to start" -// and the likes - https://serverfault.com/questions/845471/service-start-request-repeated-too-quickly-refusing-to-start-limit -// afterEach(async () => { -// await systemctl.reload() -// }) +before(async () => { + networkmanager = new NetworkManager() + await networkmanager.init() +}) test("scan", async () => { - await scan() + await networkmanager.scan() }) describe("getWifis", async () => { test("returns PlanktoScope own wifi", async (t) => { - const wifis = await getWifis() - console.log(wifis) + const wifis = await networkmanager.getWifis() const wifi = wifis.find((wifi) => wifi.ssid == "PlanktoScope fork-wave") t.assert.ok(wifi) }) }) -// after(async () => { -// await systemctl.deinit() -// systemctl = null -// }) +after(async () => { + await networkmanager.deinit() + networkmanager = null +}) diff --git a/lib/systemctl.js b/lib/systemctl.js index 52b4e2c51..7f91d710d 100644 --- a/lib/systemctl.js +++ b/lib/systemctl.js @@ -53,9 +53,10 @@ export class Systemctl { async deinit() { this.systemd_manager?.off("JobRemoved", this.#onJobRemoved) this.job_removed_handlers.clear() - this.system_bus?.connection.end() + this.system_bus.connection.end() this.system_bus = null this.systemd_manager = null + this.service.bus.connection.end() this.service = null } @@ -113,3 +114,11 @@ function normalizeUnitName(name) { if (name.endsWith(".service")) return name return name + ".service" } + +/* eslint-disable n/no-top-level-await */ +if (import.meta.main) { + const sysetmctl = new Systemctl() + await sysetmctl.init() + await sysetmctl.deinit() +} +/* eslint-enable n/no-top-level-await */ From 6cb8a919e84ffaa6f81fd4f8dea3d7202591f049 Mon Sep 17 00:00:00 2001 From: Sonny Piers Date: Tue, 9 Dec 2025 13:37:40 +0000 Subject: [PATCH 4/6] f --- backend/src/config.js | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/backend/src/config.js b/backend/src/config.js index 0b65b0e06..8b7ab76ee 100644 --- a/backend/src/config.js +++ b/backend/src/config.js @@ -1,38 +1,36 @@ import { publish, procedure } from "../../lib/mqtt.js" -import { - connectToWifi, - DeviceWireless, - getWifis, - scan, -} from "../../lib/network.js" +import { NetworkManager } from "../../lib/network.js" + +const networkmanager = new NetworkManager() +await networkmanager.init() async function publishAccessPoints() { try { - const wifis = await getWifis() + const wifis = await networkmanager.getWifis() publish("config/wifis", wifis, null, { retain: true }) } catch (err) { console.error(err) } } -DeviceWireless.on("AccessPointAdded", (/*access_point*/) => { +networkmanager.DeviceWireless.on("AccessPointAdded", (/*access_point*/) => { publishAccessPoints() }) -DeviceWireless.on("AccessPointRemoved", (/*access_point*/) => { +networkmanager.DeviceWireless.on("AccessPointRemoved", (/*access_point*/) => { publishAccessPoints() }) await procedure("config/wifis/scan", async () => { - await scan() + await networkmanager.scan() }) await procedure("config/wifis/connect", async (data) => { - await connectToWifi(data.path) + await networkmanager.connectToWifi(data.path) }) ;(async () => { await publishAccessPoints() - await scan() + await networkmanager.scan() await publishAccessPoints() })() From 81eefcbe2432fc6e39420a419f7e23fae42d2542 Mon Sep 17 00:00:00 2001 From: Sonny Piers Date: Wed, 10 Dec 2025 11:22:43 +0000 Subject: [PATCH 5/6] f --- backend/src/network/README.md | 56 +++++++++++++++++++ backend/src/{config.js => network/network.js} | 12 ++-- backend/src/service.js | 2 +- 3 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 backend/src/network/README.md rename backend/src/{config.js => network/network.js} (68%) diff --git a/backend/src/network/README.md b/backend/src/network/README.md new file mode 100644 index 000000000..a777491bb --- /dev/null +++ b/backend/src/network/README.md @@ -0,0 +1,56 @@ +# network service + +This is an MQTT service that is started as part of `bakcend`. + +### API + +### list + +**topic** `network/wifi/list` + +**payload:** +```json +[ + { + "ssid": "FOO", + "frequency": 2412, + "strength": 55, + "path": "/org/freedesktop/NetworkManager/AccessPoint/23" + }, + { + "ssid": "BAR", + "frequency": 5500, + "strength": 75, + "path": "/org/freedesktop/NetworkManager/AccessPoint/24" + } + ... +] +``` + +Subscribe to this topic to get the list of available access points. +The list will be automatically published to new subscribers and can be updated at any point. + +See `scan` below to request an explicit update. + +### scan + +**topic** `network/wifi/scan` + +Publish a request to this topic to have the wireless device scan for networks. +The response is emitted once the scan is done and contains no payload. + +This is likely to cause an update on `network/wifi/list`. + +### connect + +**topic** `network/wifi/connect` + +**payload:** +```json +{ + "path": "/org/freedesktop/NetworkManager/AccessPoint/24" // a path from the list of wifis +} +``` + +Publish a request to this topic to have the wireless device connect to the specified access point. +The response is emitted once the connection is established and contains no payload. diff --git a/backend/src/config.js b/backend/src/network/network.js similarity index 68% rename from backend/src/config.js rename to backend/src/network/network.js index 8b7ab76ee..e84d71ba5 100644 --- a/backend/src/config.js +++ b/backend/src/network/network.js @@ -1,6 +1,6 @@ -import { publish, procedure } from "../../lib/mqtt.js" +import { publish, procedure } from "../../../lib/mqtt.js" -import { NetworkManager } from "../../lib/network.js" +import { NetworkManager } from "../../../lib/network.js" const networkmanager = new NetworkManager() await networkmanager.init() @@ -8,7 +8,7 @@ await networkmanager.init() async function publishAccessPoints() { try { const wifis = await networkmanager.getWifis() - publish("config/wifis", wifis, null, { retain: true }) + publish("network/wifi/list", wifis, null, { retain: true }) } catch (err) { console.error(err) } @@ -22,13 +22,15 @@ networkmanager.DeviceWireless.on("AccessPointRemoved", (/*access_point*/) => { publishAccessPoints() }) -await procedure("config/wifis/scan", async () => { +await procedure("network/wifi/scan", async () => { await networkmanager.scan() }) -await procedure("config/wifis/connect", async (data) => { +await procedure("network/wifi/connect", async (data) => { await networkmanager.connectToWifi(data.path) }) + +// ;(async () => { await publishAccessPoints() await networkmanager.scan() diff --git a/backend/src/service.js b/backend/src/service.js index 4c442c396..85e8fe74d 100755 --- a/backend/src/service.js +++ b/backend/src/service.js @@ -7,7 +7,7 @@ import cors from "cors" import "./factory.js" import "./setup.js" -import "./config.js" +import "./network/network.js" import "./led-operating-time.js" import { readSoftwareConfig, removeConfig } from "../../lib/file-config.js" import { capture } from "../../lib/scope.js" From e34a56b60ae909ded35c8a4f7fd398742eae0a14 Mon Sep 17 00:00:00 2001 From: Sonny Piers Date: Thu, 18 Dec 2025 17:48:19 +0100 Subject: [PATCH 6/6] f --- lib/network.js | 17 +++--- lib/network.test.js | 9 ++- lib/package-lock.json | 44 +++++++++------ lib/systemctl.js | 124 ------------------------------------------ 4 files changed, 42 insertions(+), 152 deletions(-) delete mode 100644 lib/systemctl.js diff --git a/lib/network.js b/lib/network.js index 50f433870..9c93908e8 100644 --- a/lib/network.js +++ b/lib/network.js @@ -47,6 +47,8 @@ export class NetworkManager { } async scan() { + const { DeviceWireless_Properties, DeviceWireless } = this + const deferred = Promise.withResolvers() // > To know when the scan is finished, use the "PropertiesChanged" signal from "org.freedesktop.DBus.Properties" to listen to changes to the "LastScan" property. @@ -62,16 +64,15 @@ export class NetworkManager { }) if (!LastScan) return - this.DeviceWireless_Properties.unsubscribe( - "PropertiesChanged", - handler, - ).then(deferred.resolve, deferred.reject) + deferred.resolve() + DeviceWireless_Properties.unsubscribe("PropertiesChanged", handler).catch( + () => {}, + ) } - await this.DeviceWireless_Properties.subscribe("PropertiesChanged", handler) - - await this.DeviceWireless.RequestScan({}) + await DeviceWireless_Properties.subscribe("PropertiesChanged", handler) - return deferred.promise + await DeviceWireless.RequestScan({}) + await deferred.promise } async getWifis() { diff --git a/lib/network.test.js b/lib/network.test.js index dedb45c19..f193cbd79 100644 --- a/lib/network.test.js +++ b/lib/network.test.js @@ -1,8 +1,9 @@ import { describe, test, before, after } from "node:test" +import { readFile } from "node:fs/promises" + import { NetworkManager } from "./network.js" let networkmanager = null -// const service = "mediamtx" before(async () => { networkmanager = new NetworkManager() @@ -15,8 +16,12 @@ test("scan", async () => { describe("getWifis", async () => { test("returns PlanktoScope own wifi", async (t) => { + const machine_name = await readFile("/var/run/machine-name", "utf8") + const wifis = await networkmanager.getWifis() - const wifi = wifis.find((wifi) => wifi.ssid == "PlanktoScope fork-wave") + const wifi = wifis.find( + (wifi) => wifi.ssid == `PlanktoScope ${machine_name}`, + ) t.assert.ok(wifi) }) }) diff --git a/lib/package-lock.json b/lib/package-lock.json index 6aa2af1b6..07dc077c0 100644 --- a/lib/package-lock.json +++ b/lib/package-lock.json @@ -1,11 +1,9 @@ { "name": "lib", - "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "lib", "dependencies": { "@js-temporal/polyfill": "^0.5.1", "csv-parse": "^6.1.0", @@ -16,13 +14,9 @@ "mime": "^4.1.0", "mqtt": "^5.14.1", "p-event": "^7.0.0", -<<<<<<< HEAD - "promise-port": "^1.0.1" -======= "systemctl.js": "^0.1.0", "wait-port": "^1.1.0", "zod": "^4.1.13" ->>>>>>> main }, "devDependencies": { "@eslint/js": "^9.38.0", @@ -394,7 +388,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -596,7 +589,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -619,7 +611,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -632,9 +623,17 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, + "node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || >=14" + } + }, "node_modules/commist": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/commist/-/commist-3.2.0.tgz", @@ -1424,7 +1423,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -2252,12 +2250,6 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "license": "MIT" }, - "node_modules/promise-port": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-port/-/promise-port-1.0.1.tgz", - "integrity": "sha512-C3OxFrV+hH+ZTru48dHjI6qRCq+4eTbLVTu80/ig03Nvs703rzQbo+dHrI5tp/cDckSTfPNULottEwVKZje0zQ==", - "license": "ISC" - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -2566,7 +2558,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -2732,6 +2723,23 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/wait-port": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/wait-port/-/wait-port-1.1.0.tgz", + "integrity": "sha512-3e04qkoN3LxTMLakdqeWth8nih8usyg+sf1Bgdf9wwUkp05iuK1eSY/QpLvscT/+F/gA89+LpUmmgBtesbqI2Q==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "commander": "^9.3.0", + "debug": "^4.3.4" + }, + "bin": { + "wait-port": "bin/wait-port.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/lib/systemctl.js b/lib/systemctl.js deleted file mode 100644 index 7f91d710d..000000000 --- a/lib/systemctl.js +++ /dev/null @@ -1,124 +0,0 @@ -import { getSystemBus } from "./dbus.js" - -export class Systemctl { - system_bus = null - systemd_manager = null - service = null - job_removed_handlers = new Map() - - async init() { - if (this.systemd_manager) return - - const system_bus = getSystemBus() - this.system_bus = system_bus - const service = system_bus.getService("org.freedesktop.systemd1") - this.service = service - - const systemd_manager = await service.getInterface( - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - ) - systemd_manager.on("JobRemoved", this.#onJobRemoved) - - await systemd_manager.Subscribe() - this.systemd_manager = systemd_manager - } - - #onJobRemoved = (job_id, job_path, unit, result) => { - // TODO: Ugly setTimeout but we are hitting an occasional race condtion without it - // where await stopService returns the job after the relevant JobRemoved event is triggered - // systemd warns about this - // > This can be achieved in a race-free manner by first subscribing to the JobRemoved() signal, then calling StartUnit() and using the returned job object to filter out unrelated JobRemoved() signals, until the desired one is received, which will then carry the result of the start operation. - // https://www.freedesktop.org/software/systemd/man/latest/org.freedesktop.systemd1.html - // this is exactly what we do but probably we're just too slow at returning from DBus methods - setTimeout(() => { - const handler = this.job_removed_handlers.get(job_path) - if (!handler) return - - if (result == "done") { - handler.resolve(result) - } else { - handler.reject( - new Error( - `Job "${job_id}" for unit "${unit}" failed with result "${result}". `, - ), - ) - } - - this.job_removed_handlers.delete(job_path) - }) - } - - // TODO: real async - async deinit() { - this.systemd_manager?.off("JobRemoved", this.#onJobRemoved) - this.job_removed_handlers.clear() - this.system_bus.connection.end() - this.system_bus = null - this.systemd_manager = null - this.service.bus.connection.end() - this.service = null - } - - #createJobHandler(job) { - const handler = Promise.withResolvers() - this.job_removed_handlers.set(job, handler) - return handler.promise - } - - async restart(name) { - const job = await this.systemd_manager.RestartUnit( - normalizeUnitName(name), - "replace", - ) - return this.#createJobHandler(job) - } - - async stop(name) { - const job = await this.systemd_manager.StopUnit( - normalizeUnitName(name), - "replace", - ) - return this.#createJobHandler(job) - } - - async start(name) { - const job = await this.systemd_manager.StartUnit( - normalizeUnitName(name), - "replace", - ) - return this.#createJobHandler(job) - } - - async enable(units = []) { - await this.systemd_manager.EnableUnitFiles( - units.map(normalizeUnitName), - false, // runtime - false, // force - ) - } - - async disable(units = []) { - await this.systemd_manager.DisableUnitFiles( - units.map(normalizeUnitName), - false, // runtime - ) - } - - async reload() { - await this.systemd_manager.Reload() - } -} - -function normalizeUnitName(name) { - if (name.endsWith(".service")) return name - return name + ".service" -} - -/* eslint-disable n/no-top-level-await */ -if (import.meta.main) { - const sysetmctl = new Systemctl() - await sysetmctl.init() - await sysetmctl.deinit() -} -/* eslint-enable n/no-top-level-await */