signalk-container-helper 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -6
- package/dist/device-watch.d.ts +107 -0
- package/dist/device-watch.d.ts.map +1 -0
- package/dist/device-watch.js +139 -0
- package/dist/device-watch.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/serial-devices.d.ts +26 -0
- package/dist/serial-devices.d.ts.map +1 -0
- package/dist/serial-devices.js +33 -0
- package/dist/serial-devices.js.map +1 -0
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -154,12 +154,14 @@ rewrites `app.getDataDirPath()` per plugin, and signalk-container resolves that
|
|
|
154
154
|
field from _its own_ app object — so you get
|
|
155
155
|
`<configRoot>/plugin-config-data/signalk-container/`, not yours.
|
|
156
156
|
|
|
157
|
-
| You want
|
|
158
|
-
|
|
|
159
|
-
| A private writable area in the SK data tree
|
|
160
|
-
| The whole SK config root
|
|
161
|
-
| **Your own plugin's data dir**
|
|
162
|
-
| **Is there a GPU / device on the host?**
|
|
157
|
+
| You want | Use |
|
|
158
|
+
| --------------------------------------------- | ------------------------------------------------ |
|
|
159
|
+
| A private writable area in the SK data tree | `signalkDataMount` (signalk-container's) |
|
|
160
|
+
| The whole SK config root | `signalkConfigRootMount` (incl. `security.json`) |
|
|
161
|
+
| **Your own plugin's data dir** | **`resolveMount()`** + a `volumes` entry |
|
|
162
|
+
| **Is there a GPU / device on the host?** | **`probeHostDevice()`** |
|
|
163
|
+
| **Which USB serial devices are plugged in?** | **`listHostSerialDevices()`** |
|
|
164
|
+
| **Act when a USB device is pulled/replugged** | **`watchHostDevice()`** |
|
|
163
165
|
|
|
164
166
|
Either mount field resolves to a host path or a named volume, depending on the
|
|
165
167
|
deployment. A named volume always arrives whole: signalk-container sends no
|
|
@@ -192,6 +194,58 @@ report it as absent. Throws `ContainerHelperError('unsupported-manager')` on
|
|
|
192
194
|
signalk-container older than 1.30.0, so catch it if you would rather degrade
|
|
193
195
|
than fail. Works on both Docker and rootless podman.
|
|
194
196
|
|
|
197
|
+
### USB devices that come and go
|
|
198
|
+
|
|
199
|
+
A container gets its device nodes when it is created. Pull a USB stick while
|
|
200
|
+
it runs and the program inside usually exits, and `unless-stopped` restarts it
|
|
201
|
+
against a node that is gone; plug it back in and it may come up as another
|
|
202
|
+
node (`ttyUSB1` for `ttyUSB0`) that the old container never sees.
|
|
203
|
+
|
|
204
|
+
`listHostSerialDevices(manager)` lists the host's `/dev/serial/by-id` links as
|
|
205
|
+
full, sorted paths. Their names carry vendor, product and serial number, so a
|
|
206
|
+
plugin can recognise its hardware without asking the user for a path, and the
|
|
207
|
+
link is the path to pass to the container: it survives a replug that changes
|
|
208
|
+
the `ttyUSB` number. `[]` means nothing is plugged in, `null` that the host
|
|
209
|
+
could not be asked. A containerized Signal K needs signalk-container 1.37.2 or
|
|
210
|
+
later to list the links.
|
|
211
|
+
|
|
212
|
+
`watchHostDevice(options)` follows one device and tells the plugin when to act;
|
|
213
|
+
what acting means stays with the plugin:
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
import { watchHostDevice } from "signalk-container-helper";
|
|
217
|
+
|
|
218
|
+
const watch = watchHostDevice({
|
|
219
|
+
manager,
|
|
220
|
+
path: "/dev/serial/by-id/usb-ITead_Sonoff_Zigbee_3.0_USB_Dongle_Plus_…",
|
|
221
|
+
isStarted: () => started,
|
|
222
|
+
isHealthy: () => bridge.online, // undefined when you cannot tell
|
|
223
|
+
onEvent: async (event) => {
|
|
224
|
+
if (event === "unplugged") await manager.stop(name);
|
|
225
|
+
else await container.applyUpdate(tag); // pluggedIn or stuck: recreate
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
// in plugin.stop():
|
|
229
|
+
await watch.stop();
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
| Event | When |
|
|
233
|
+
| ----------- | ----------------------------------------------------------------------------------------- |
|
|
234
|
+
| `unplugged` | The device went away under a started container. Once per unplug. |
|
|
235
|
+
| `pluggedIn` | The device is back after an unplug, or appeared while nothing was started. |
|
|
236
|
+
| `stuck` | The device is there and the workload stayed unhealthy for `stuckMs`. Repeats until fixed. |
|
|
237
|
+
|
|
238
|
+
The host is probed only while the container is not started or not healthy,
|
|
239
|
+
so a working setup costs nothing (a probe is a short-lived container under a
|
|
240
|
+
containerized Signal K). A probe that cannot tell changes nothing, and with
|
|
241
|
+
health unknown no `stuck` is raised. Ticks run every `intervalMs` (default
|
|
242
|
+
30 s, the first one interval after the call), handlers never overlap, a
|
|
243
|
+
throwing handler is logged through `debug`, and `stop()` waits for a tick in
|
|
244
|
+
progress (from inside `onEvent` it returns at once). Pass
|
|
245
|
+
`initiallyAbsent: true` when the device was missing at start, so its arrival
|
|
246
|
+
is reported as `pluggedIn`. `decideDeviceEvent` is the pure decision behind
|
|
247
|
+
it, exported for plugins that run their own timer.
|
|
248
|
+
|
|
195
249
|
`resolveMount` translates any absolute host path into a mount the container
|
|
196
250
|
runtime can actually bind, on bare-metal and containerized Signal K alike:
|
|
197
251
|
|
|
@@ -783,6 +837,9 @@ Adopt these even where you don't use the components:
|
|
|
783
837
|
| `managedModeSchema(opts)` | The two config fields as plain JSON Schema + matching defaults (`signalk-container-helper/schema`) |
|
|
784
838
|
| `waitForHttpReady(url, opts)` | Poll until 2xx or deadline (throws) |
|
|
785
839
|
| `retryForever(fn, opts)` | Retry until success — 15s doubling to a 120s ceiling, no attempt cap. `ManagedContainer` takes it as `readinessRetry`; exported standalone for work this library does not manage |
|
|
840
|
+
| `probeHostDevice(manager, path)` | Whether the host has a device, with its node names and groups; `null` when unknown |
|
|
841
|
+
| `listHostSerialDevices(manager)` | The host's `/dev/serial/by-id` links as full paths; `[]` when none, `null` when unknown |
|
|
842
|
+
| `watchHostDevice(opts)` | Poll a host device while its container is unhealthy or not started; reports `unplugged`, `pluggedIn`, `stuck` (see [USB devices that come and go](#usb-devices-that-come-and-go)) |
|
|
786
843
|
| `anySignal(signals)` | Compose several `AbortSignal`s into one that aborts when any does (`undefined` when none are given) |
|
|
787
844
|
| `probeHttpHealth(url, opts)` | Retrying liveness probe with slow-response detection (never throws) |
|
|
788
845
|
| `fetchWithTimeout(url, opts)` | `fetch` with an `AbortController` timeout |
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Following a host device that can be unplugged while its container runs.
|
|
3
|
+
*
|
|
4
|
+
* A container gets its device nodes when it is created. When the device is
|
|
5
|
+
* pulled, the program inside usually exits, and `unless-stopped` restarts it
|
|
6
|
+
* against a node that is gone, over and over. When the device comes back it
|
|
7
|
+
* may get another node (ttyUSB1 for ttyUSB0), which the old container never
|
|
8
|
+
* sees. Nothing in the runtime notices either event, so the plugin has to.
|
|
9
|
+
*
|
|
10
|
+
* `watchHostDevice` polls the host through {@link probeHostDevice} and tells
|
|
11
|
+
* the plugin when to stop its container, when to (re)create it, and when a
|
|
12
|
+
* container that has its device has stayed unhealthy too long. What "stop" and
|
|
13
|
+
* "recreate" mean stays with the plugin.
|
|
14
|
+
*/
|
|
15
|
+
import type { ContainerManagerApi } from "./types.js";
|
|
16
|
+
/** Default poll interval. signalk-container caches a probe for 60 s. */
|
|
17
|
+
export declare const DEVICE_WATCH_INTERVAL_MS = 30000;
|
|
18
|
+
/** Default time a container may stay unhealthy with its device present. */
|
|
19
|
+
export declare const DEVICE_STUCK_MS = 180000;
|
|
20
|
+
export interface DeviceWatchState {
|
|
21
|
+
/** The device was missing on the last tick that could tell. */
|
|
22
|
+
absent: boolean;
|
|
23
|
+
/** When the container was first seen unhealthy with its device present. */
|
|
24
|
+
unhealthySince?: number;
|
|
25
|
+
}
|
|
26
|
+
export interface DeviceObservation {
|
|
27
|
+
/** The host probe: undefined when it could not tell. */
|
|
28
|
+
present: boolean | undefined;
|
|
29
|
+
/** The plugin has created the container. */
|
|
30
|
+
started: boolean;
|
|
31
|
+
/** The workload's health; undefined when the plugin cannot tell. */
|
|
32
|
+
healthy: boolean | undefined;
|
|
33
|
+
now: number;
|
|
34
|
+
stuckMs: number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* - `unplugged`: the device went away under a started container.
|
|
38
|
+
* - `pluggedIn`: the device is back (or appeared for the first time).
|
|
39
|
+
* - `stuck`: the device is there, the container is not healthy.
|
|
40
|
+
*/
|
|
41
|
+
export type DeviceWatchEvent = "unplugged" | "pluggedIn" | "stuck";
|
|
42
|
+
/** Whether a tick needs to ask the host at all. */
|
|
43
|
+
export declare function deviceProbeNeeded(started: boolean, healthy: boolean | undefined): boolean;
|
|
44
|
+
/** The decision of one tick, and the state for the next one. */
|
|
45
|
+
export declare function decideDeviceEvent(state: DeviceWatchState, obs: DeviceObservation): {
|
|
46
|
+
event: DeviceWatchEvent | undefined;
|
|
47
|
+
next: DeviceWatchState;
|
|
48
|
+
};
|
|
49
|
+
export interface WatchHostDeviceOptions {
|
|
50
|
+
manager: ContainerManagerApi;
|
|
51
|
+
/** Host path of the device, ideally a `/dev/serial/by-id/…` link. */
|
|
52
|
+
path: string;
|
|
53
|
+
/** Whether the plugin has created the container for this device. */
|
|
54
|
+
isStarted: () => boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Whether the workload works. Return undefined when the plugin cannot tell
|
|
57
|
+
* (its own connection to the workload is down): no `stuck` is raised then.
|
|
58
|
+
*/
|
|
59
|
+
isHealthy: () => boolean | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* Called for each event, never while the previous call runs and never after
|
|
62
|
+
* `stop()`. `unplugged` and `pluggedIn` fire once per transition; `stuck`
|
|
63
|
+
* repeats every `stuckMs` while the workload stays unhealthy. A rejection is
|
|
64
|
+
* logged through `debug` and the watch moves on: a recreate that failed shows
|
|
65
|
+
* up again as `stuck`.
|
|
66
|
+
*/
|
|
67
|
+
onEvent: (event: DeviceWatchEvent) => Promise<void> | void;
|
|
68
|
+
/** The device was missing when the plugin last looked. Default false. */
|
|
69
|
+
initiallyAbsent?: boolean;
|
|
70
|
+
intervalMs?: number;
|
|
71
|
+
stuckMs?: number;
|
|
72
|
+
debug?: (msg: string) => void;
|
|
73
|
+
}
|
|
74
|
+
export interface HostDeviceWatch {
|
|
75
|
+
/**
|
|
76
|
+
* Stops polling and waits for a tick in progress. Called from inside
|
|
77
|
+
* `onEvent` it returns at once instead of waiting for itself.
|
|
78
|
+
*/
|
|
79
|
+
stop(): Promise<void>;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Poll the host for `path` and report its transitions.
|
|
83
|
+
*
|
|
84
|
+
* ```ts
|
|
85
|
+
* const watch = watchHostDevice({
|
|
86
|
+
* manager,
|
|
87
|
+
* path: "/dev/serial/by-id/usb-…",
|
|
88
|
+
* isStarted: () => started,
|
|
89
|
+
* isHealthy: () => bridge.online,
|
|
90
|
+
* onEvent: async (event) => {
|
|
91
|
+
* if (event === "unplugged") await manager.stop(name);
|
|
92
|
+
* else await container.applyUpdate(tag); // recreate with the new node
|
|
93
|
+
* },
|
|
94
|
+
* });
|
|
95
|
+
* // plugin.stop():
|
|
96
|
+
* await watch.stop();
|
|
97
|
+
* ```
|
|
98
|
+
*
|
|
99
|
+
* A healthy started container is never probed, so the cost of a probe (a
|
|
100
|
+
* short-lived container under a containerized Signal K) is paid only while
|
|
101
|
+
* something is wrong. A probe that cannot tell (`null`, or an older
|
|
102
|
+
* signalk-container) changes nothing. The first tick runs one interval after
|
|
103
|
+
* the call. `stuck` repeats every `stuckMs` (plus up to one interval) for as
|
|
104
|
+
* long as the workload stays unhealthy with its device present.
|
|
105
|
+
*/
|
|
106
|
+
export declare function watchHostDevice(options: WatchHostDeviceOptions): HostDeviceWatch;
|
|
107
|
+
//# sourceMappingURL=device-watch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-watch.d.ts","sourceRoot":"","sources":["../src/device-watch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEtD,wEAAwE;AACxE,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAC/C,2EAA2E;AAC3E,eAAO,MAAM,eAAe,SAAU,CAAC;AAEvC,MAAM,WAAW,gBAAgB;IAC/B,+DAA+D;IAC/D,MAAM,EAAE,OAAO,CAAC;IAChB,2EAA2E;IAC3E,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,iBAAiB;IAChC,wDAAwD;IACxD,OAAO,EAAE,OAAO,GAAG,SAAS,CAAC;IAC7B,4CAA4C;IAC5C,OAAO,EAAE,OAAO,CAAC;IACjB,oEAAoE;IACpE,OAAO,EAAE,OAAO,GAAG,SAAS,CAAC;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG,WAAW,GAAG,OAAO,CAAC;AAEnE,mDAAmD;AACnD,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,OAAO,GAAG,SAAS,GAC3B,OAAO,CAET;AAED,gEAAgE;AAChE,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,gBAAgB,EACvB,GAAG,EAAE,iBAAiB,GACrB;IAAE,KAAK,EAAE,gBAAgB,GAAG,SAAS,CAAC;IAAC,IAAI,EAAE,gBAAgB,CAAA;CAAE,CAuBjE;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,mBAAmB,CAAC;IAC7B,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,SAAS,EAAE,MAAM,OAAO,CAAC;IACzB;;;OAGG;IACH,SAAS,EAAE,MAAM,OAAO,GAAG,SAAS,CAAC;IACrC;;;;;;OAMG;IACH,OAAO,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC3D,yEAAyE;IACzE,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,sBAAsB,GAC9B,eAAe,CA0DjB"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Following a host device that can be unplugged while its container runs.
|
|
3
|
+
*
|
|
4
|
+
* A container gets its device nodes when it is created. When the device is
|
|
5
|
+
* pulled, the program inside usually exits, and `unless-stopped` restarts it
|
|
6
|
+
* against a node that is gone, over and over. When the device comes back it
|
|
7
|
+
* may get another node (ttyUSB1 for ttyUSB0), which the old container never
|
|
8
|
+
* sees. Nothing in the runtime notices either event, so the plugin has to.
|
|
9
|
+
*
|
|
10
|
+
* `watchHostDevice` polls the host through {@link probeHostDevice} and tells
|
|
11
|
+
* the plugin when to stop its container, when to (re)create it, and when a
|
|
12
|
+
* container that has its device has stayed unhealthy too long. What "stop" and
|
|
13
|
+
* "recreate" mean stays with the plugin.
|
|
14
|
+
*/
|
|
15
|
+
import { probeHostDevice } from "./host-device.js";
|
|
16
|
+
import { errMsg } from "./util.js";
|
|
17
|
+
/** Default poll interval. signalk-container caches a probe for 60 s. */
|
|
18
|
+
export const DEVICE_WATCH_INTERVAL_MS = 30_000;
|
|
19
|
+
/** Default time a container may stay unhealthy with its device present. */
|
|
20
|
+
export const DEVICE_STUCK_MS = 180_000;
|
|
21
|
+
/** Whether a tick needs to ask the host at all. */
|
|
22
|
+
export function deviceProbeNeeded(started, healthy) {
|
|
23
|
+
return !started || healthy !== true;
|
|
24
|
+
}
|
|
25
|
+
/** The decision of one tick, and the state for the next one. */
|
|
26
|
+
export function decideDeviceEvent(state, obs) {
|
|
27
|
+
if (obs.started && obs.healthy === true) {
|
|
28
|
+
return { event: undefined, next: { absent: false } };
|
|
29
|
+
}
|
|
30
|
+
if (obs.present === undefined)
|
|
31
|
+
return { event: undefined, next: state };
|
|
32
|
+
if (!obs.present) {
|
|
33
|
+
return {
|
|
34
|
+
event: obs.started && !state.absent ? "unplugged" : undefined,
|
|
35
|
+
next: { absent: true },
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
if (state.absent || !obs.started) {
|
|
39
|
+
return {
|
|
40
|
+
event: state.absent ? "pluggedIn" : undefined,
|
|
41
|
+
next: { absent: false },
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
if (obs.healthy === undefined)
|
|
45
|
+
return { event: undefined, next: state };
|
|
46
|
+
const unhealthySince = state.unhealthySince ?? obs.now;
|
|
47
|
+
if (obs.now - unhealthySince >= obs.stuckMs) {
|
|
48
|
+
return { event: "stuck", next: { absent: false } };
|
|
49
|
+
}
|
|
50
|
+
return { event: undefined, next: { absent: false, unhealthySince } };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Poll the host for `path` and report its transitions.
|
|
54
|
+
*
|
|
55
|
+
* ```ts
|
|
56
|
+
* const watch = watchHostDevice({
|
|
57
|
+
* manager,
|
|
58
|
+
* path: "/dev/serial/by-id/usb-…",
|
|
59
|
+
* isStarted: () => started,
|
|
60
|
+
* isHealthy: () => bridge.online,
|
|
61
|
+
* onEvent: async (event) => {
|
|
62
|
+
* if (event === "unplugged") await manager.stop(name);
|
|
63
|
+
* else await container.applyUpdate(tag); // recreate with the new node
|
|
64
|
+
* },
|
|
65
|
+
* });
|
|
66
|
+
* // plugin.stop():
|
|
67
|
+
* await watch.stop();
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* A healthy started container is never probed, so the cost of a probe (a
|
|
71
|
+
* short-lived container under a containerized Signal K) is paid only while
|
|
72
|
+
* something is wrong. A probe that cannot tell (`null`, or an older
|
|
73
|
+
* signalk-container) changes nothing. The first tick runs one interval after
|
|
74
|
+
* the call. `stuck` repeats every `stuckMs` (plus up to one interval) for as
|
|
75
|
+
* long as the workload stays unhealthy with its device present.
|
|
76
|
+
*/
|
|
77
|
+
export function watchHostDevice(options) {
|
|
78
|
+
const debug = options.debug ?? (() => undefined);
|
|
79
|
+
const stuckMs = options.stuckMs ?? DEVICE_STUCK_MS;
|
|
80
|
+
let state = { absent: options.initiallyAbsent ?? false };
|
|
81
|
+
let stopped = false;
|
|
82
|
+
let tick;
|
|
83
|
+
let inHandler = false;
|
|
84
|
+
const once = async () => {
|
|
85
|
+
const started = options.isStarted();
|
|
86
|
+
const healthy = options.isHealthy();
|
|
87
|
+
let present;
|
|
88
|
+
if (deviceProbeNeeded(started, healthy)) {
|
|
89
|
+
try {
|
|
90
|
+
present = (await probeHostDevice(options.manager, options.path))
|
|
91
|
+
?.exists;
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
debug(`watchHostDevice: probing ${options.path}: ${errMsg(err)}`);
|
|
95
|
+
}
|
|
96
|
+
if (stopped)
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const { event, next } = decideDeviceEvent(state, {
|
|
100
|
+
present,
|
|
101
|
+
started,
|
|
102
|
+
healthy,
|
|
103
|
+
now: Date.now(),
|
|
104
|
+
stuckMs,
|
|
105
|
+
});
|
|
106
|
+
state = next;
|
|
107
|
+
if (event === undefined)
|
|
108
|
+
return;
|
|
109
|
+
debug(`watchHostDevice: ${options.path} ${event}`);
|
|
110
|
+
inHandler = true;
|
|
111
|
+
try {
|
|
112
|
+
await options.onEvent(event);
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
inHandler = false;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
const timer = setInterval(() => {
|
|
119
|
+
if (stopped || tick !== undefined)
|
|
120
|
+
return;
|
|
121
|
+
tick = once()
|
|
122
|
+
.catch((err) => {
|
|
123
|
+
debug(`watchHostDevice: ${options.path}: ${errMsg(err)}`);
|
|
124
|
+
})
|
|
125
|
+
.finally(() => {
|
|
126
|
+
tick = undefined;
|
|
127
|
+
});
|
|
128
|
+
}, options.intervalMs ?? DEVICE_WATCH_INTERVAL_MS);
|
|
129
|
+
return {
|
|
130
|
+
async stop() {
|
|
131
|
+
stopped = true;
|
|
132
|
+
clearInterval(timer);
|
|
133
|
+
// Awaiting the tick from inside its own handler would never resolve.
|
|
134
|
+
if (!inHandler)
|
|
135
|
+
await tick;
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=device-watch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-watch.js","sourceRoot":"","sources":["../src/device-watch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAGnC,wEAAwE;AACxE,MAAM,CAAC,MAAM,wBAAwB,GAAG,MAAM,CAAC;AAC/C,2EAA2E;AAC3E,MAAM,CAAC,MAAM,eAAe,GAAG,OAAO,CAAC;AA2BvC,mDAAmD;AACnD,MAAM,UAAU,iBAAiB,CAC/B,OAAgB,EAChB,OAA4B;IAE5B,OAAO,CAAC,OAAO,IAAI,OAAO,KAAK,IAAI,CAAC;AACtC,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,iBAAiB,CAC/B,KAAuB,EACvB,GAAsB;IAEtB,IAAI,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;QACxC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;IACvD,CAAC;IACD,IAAI,GAAG,CAAC,OAAO,KAAK,SAAS;QAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IACxE,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QACjB,OAAO;YACL,KAAK,EAAE,GAAG,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS;YAC7D,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE;SACvB,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QACjC,OAAO;YACL,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS;YAC7C,IAAI,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE;SACxB,CAAC;IACJ,CAAC;IACD,IAAI,GAAG,CAAC,OAAO,KAAK,SAAS;QAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IACxE,MAAM,cAAc,GAAG,KAAK,CAAC,cAAc,IAAI,GAAG,CAAC,GAAG,CAAC;IACvD,IAAI,GAAG,CAAC,GAAG,GAAG,cAAc,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;QAC5C,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;IACrD,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE,CAAC;AACvE,CAAC;AAoCD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,eAAe,CAC7B,OAA+B;IAE/B,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IACjD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,eAAe,CAAC;IACnD,IAAI,KAAK,GAAqB,EAAE,MAAM,EAAE,OAAO,CAAC,eAAe,IAAI,KAAK,EAAE,CAAC;IAC3E,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,IAAI,IAA+B,CAAC;IACpC,IAAI,SAAS,GAAG,KAAK,CAAC;IAEtB,MAAM,IAAI,GAAG,KAAK,IAAmB,EAAE;QACrC,MAAM,OAAO,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;QACpC,MAAM,OAAO,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;QACpC,IAAI,OAA4B,CAAC;QACjC,IAAI,iBAAiB,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC;YACxC,IAAI,CAAC;gBACH,OAAO,GAAG,CAAC,MAAM,eAAe,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;oBAC9D,EAAE,MAAM,CAAC;YACb,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,KAAK,CAAC,4BAA4B,OAAO,CAAC,IAAI,KAAK,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACpE,CAAC;YACD,IAAI,OAAO;gBAAE,OAAO;QACtB,CAAC;QACD,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,iBAAiB,CAAC,KAAK,EAAE;YAC/C,OAAO;YACP,OAAO;YACP,OAAO;YACP,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE;YACf,OAAO;SACR,CAAC,CAAC;QACH,KAAK,GAAG,IAAI,CAAC;QACb,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO;QAChC,KAAK,CAAC,oBAAoB,OAAO,CAAC,IAAI,IAAI,KAAK,EAAE,CAAC,CAAC;QACnD,SAAS,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC;YACH,MAAM,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC/B,CAAC;gBAAS,CAAC;YACT,SAAS,GAAG,KAAK,CAAC;QACpB,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7B,IAAI,OAAO,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO;QAC1C,IAAI,GAAG,IAAI,EAAE;aACV,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;YACtB,KAAK,CAAC,oBAAoB,OAAO,CAAC,IAAI,KAAK,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC5D,CAAC,CAAC;aACD,OAAO,CAAC,GAAG,EAAE;YACZ,IAAI,GAAG,SAAS,CAAC;QACnB,CAAC,CAAC,CAAC;IACP,CAAC,EAAE,OAAO,CAAC,UAAU,IAAI,wBAAwB,CAAC,CAAC;IAEnD,OAAO;QACL,KAAK,CAAC,IAAI;YACR,OAAO,GAAG,IAAI,CAAC;YACf,aAAa,CAAC,KAAK,CAAC,CAAC;YACrB,qEAAqE;YACrE,IAAI,CAAC,SAAS;gBAAE,MAAM,IAAI,CAAC;QAC7B,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -9,6 +9,9 @@ export { buildVersionSource } from "./version-source.js";
|
|
|
9
9
|
export type { VersionSourceSpec } from "./version-source.js";
|
|
10
10
|
export { resolveMount } from "./host-path.js";
|
|
11
11
|
export { probeHostDevice } from "./host-device.js";
|
|
12
|
+
export { listHostSerialDevices, SERIAL_BY_ID_DIR } from "./serial-devices.js";
|
|
13
|
+
export { watchHostDevice, decideDeviceEvent, deviceProbeNeeded, DEVICE_WATCH_INTERVAL_MS, DEVICE_STUCK_MS, } from "./device-watch.js";
|
|
14
|
+
export type { DeviceWatchEvent, DeviceWatchState, DeviceObservation, WatchHostDeviceOptions, HostDeviceWatch, } from "./device-watch.js";
|
|
12
15
|
export type { HostMount, ResolveMountOptions } from "./host-path.js";
|
|
13
16
|
export { retryForever, anySignal } from "./retry.js";
|
|
14
17
|
export type { RetryForeverOptions } from "./retry.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAC5E,YAAY,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAC7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,YAAY,EACV,uBAAuB,EACvB,oBAAoB,EACpB,gBAAgB,EAChB,WAAW,EACX,UAAU,EACV,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,EAChB,WAAW,GACZ,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,YAAY,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACtE,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,YAAY,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,YAAY,EAAE,SAAS,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACrD,YAAY,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAChF,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,aAAa,EACb,gBAAgB,GACjB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,YAAY,EACZ,gBAAgB,EAChB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,EACL,wBAAwB,EACxB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACnE,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,YAAY,EACV,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EACjB,wBAAwB,GACzB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,SAAS,EACT,uBAAuB,EACvB,uBAAuB,EACvB,sBAAsB,EACtB,iBAAiB,GAClB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,WAAW,EACX,cAAc,EACd,MAAM,EACN,eAAe,EACf,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAC5E,YAAY,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAC7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,YAAY,EACV,uBAAuB,EACvB,oBAAoB,EACpB,gBAAgB,EAChB,WAAW,EACX,UAAU,EACV,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,EAChB,WAAW,GACZ,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,YAAY,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACtE,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,YAAY,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC9E,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,wBAAwB,EACxB,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,sBAAsB,EACtB,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,SAAS,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACrD,YAAY,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAChF,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,aAAa,EACb,gBAAgB,GACjB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,YAAY,EACZ,gBAAgB,EAChB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,EACL,wBAAwB,EACxB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACnE,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,YAAY,EACV,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EACjB,wBAAwB,GACzB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,SAAS,EACT,uBAAuB,EACvB,uBAAuB,EACvB,sBAAsB,EACtB,iBAAiB,GAClB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,WAAW,EACX,cAAc,EACd,MAAM,EACN,eAAe,EACf,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,8 @@ export { AdoptedContainer } from "./adopted-container.js";
|
|
|
5
5
|
export { buildVersionSource } from "./version-source.js";
|
|
6
6
|
export { resolveMount } from "./host-path.js";
|
|
7
7
|
export { probeHostDevice } from "./host-device.js";
|
|
8
|
+
export { listHostSerialDevices, SERIAL_BY_ID_DIR } from "./serial-devices.js";
|
|
9
|
+
export { watchHostDevice, decideDeviceEvent, deviceProbeNeeded, DEVICE_WATCH_INTERVAL_MS, DEVICE_STUCK_MS, } from "./device-watch.js";
|
|
8
10
|
export { retryForever, anySignal } from "./retry.js";
|
|
9
11
|
export { fetchWithTimeout, waitForHttpReady, probeHttpHealth } from "./http.js";
|
|
10
12
|
export { resolveEndpoint, waitForEndpointReady, isManagedMode, describeEndpoint, } from "./endpoint.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAE5E,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAY1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAE5E,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAY1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC9E,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,wBAAwB,EACxB,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAS3B,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAErD,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAChF,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,aAAa,EACb,gBAAgB,GACjB,MAAM,eAAe,CAAC;AAOvB,OAAO,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,EACL,wBAAwB,EACxB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAchD,OAAO,EACL,WAAW,EACX,cAAc,EACd,MAAM,EACN,eAAe,EACf,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finding USB serial devices on the host by name.
|
|
3
|
+
*
|
|
4
|
+
* udev gives every USB serial device a link in `/dev/serial/by-id` named after
|
|
5
|
+
* its vendor, product and serial number
|
|
6
|
+
* (`usb-ITead_Sonoff_Zigbee_3.0_USB_Dongle_Plus_…-if00-port0`), and removes it
|
|
7
|
+
* when the device is unplugged. That name is what lets a plugin recognise its
|
|
8
|
+
* hardware without asking the user for a path, and the link is the path to
|
|
9
|
+
* hand to the container: it survives a replug that changes the ttyUSB number.
|
|
10
|
+
*/
|
|
11
|
+
import type { ContainerManagerApi } from "./types.js";
|
|
12
|
+
export declare const SERIAL_BY_ID_DIR = "/dev/serial/by-id";
|
|
13
|
+
/**
|
|
14
|
+
* The `/dev/serial/by-id` links on the host, as full paths, sorted.
|
|
15
|
+
*
|
|
16
|
+
* Returns `[]` when no USB serial device is plugged in, and **null when the
|
|
17
|
+
* host could not be asked** — see {@link probeHostDevice}. With nothing
|
|
18
|
+
* plugged in udev removes the directory itself, which a containerized Signal K
|
|
19
|
+
* cannot mount, so there null usually means "none". A containerized Signal K
|
|
20
|
+
* needs signalk-container 1.37.2 or later; older ones list nothing.
|
|
21
|
+
*
|
|
22
|
+
* @throws ContainerHelperError `unsupported-manager` on signalk-container
|
|
23
|
+
* older than 1.30.0.
|
|
24
|
+
*/
|
|
25
|
+
export declare function listHostSerialDevices(manager: ContainerManagerApi): Promise<string[] | null>;
|
|
26
|
+
//# sourceMappingURL=serial-devices.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serial-devices.d.ts","sourceRoot":"","sources":["../src/serial-devices.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEtD,eAAO,MAAM,gBAAgB,sBAAsB,CAAC;AAEpD;;;;;;;;;;;GAWG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAK1B"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finding USB serial devices on the host by name.
|
|
3
|
+
*
|
|
4
|
+
* udev gives every USB serial device a link in `/dev/serial/by-id` named after
|
|
5
|
+
* its vendor, product and serial number
|
|
6
|
+
* (`usb-ITead_Sonoff_Zigbee_3.0_USB_Dongle_Plus_…-if00-port0`), and removes it
|
|
7
|
+
* when the device is unplugged. That name is what lets a plugin recognise its
|
|
8
|
+
* hardware without asking the user for a path, and the link is the path to
|
|
9
|
+
* hand to the container: it survives a replug that changes the ttyUSB number.
|
|
10
|
+
*/
|
|
11
|
+
import { probeHostDevice } from "./host-device.js";
|
|
12
|
+
export const SERIAL_BY_ID_DIR = "/dev/serial/by-id";
|
|
13
|
+
/**
|
|
14
|
+
* The `/dev/serial/by-id` links on the host, as full paths, sorted.
|
|
15
|
+
*
|
|
16
|
+
* Returns `[]` when no USB serial device is plugged in, and **null when the
|
|
17
|
+
* host could not be asked** — see {@link probeHostDevice}. With nothing
|
|
18
|
+
* plugged in udev removes the directory itself, which a containerized Signal K
|
|
19
|
+
* cannot mount, so there null usually means "none". A containerized Signal K
|
|
20
|
+
* needs signalk-container 1.37.2 or later; older ones list nothing.
|
|
21
|
+
*
|
|
22
|
+
* @throws ContainerHelperError `unsupported-manager` on signalk-container
|
|
23
|
+
* older than 1.30.0.
|
|
24
|
+
*/
|
|
25
|
+
export async function listHostSerialDevices(manager) {
|
|
26
|
+
const probe = await probeHostDevice(manager, SERIAL_BY_ID_DIR);
|
|
27
|
+
if (probe === null)
|
|
28
|
+
return null;
|
|
29
|
+
if (!probe.exists)
|
|
30
|
+
return [];
|
|
31
|
+
return probe.nodes.map((name) => `${SERIAL_BY_ID_DIR}/${name}`).sort();
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=serial-devices.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serial-devices.js","sourceRoot":"","sources":["../src/serial-devices.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGnD,MAAM,CAAC,MAAM,gBAAgB,GAAG,mBAAmB,CAAC;AAEpD;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,OAA4B;IAE5B,MAAM,KAAK,GAAG,MAAM,eAAe,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC;IAC/D,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAChC,IAAI,CAAC,KAAK,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IAC7B,OAAO,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,gBAAgB,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;AACzE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "signalk-container-helper",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Helper library for Signal K plugins that manage containers via the signalk-container plugin",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -36,8 +36,7 @@
|
|
|
36
36
|
"lint": "eslint --fix",
|
|
37
37
|
"format": "npm run prettier && npm run lint",
|
|
38
38
|
"ci-lint": "eslint && prettier --check .",
|
|
39
|
-
"prepublishOnly": "npm run build && npm test"
|
|
40
|
-
"release": "git tag -d v$npm_package_version; git tag v$npm_package_version && git push && git push origin v$npm_package_version"
|
|
39
|
+
"prepublishOnly": "npm run build && npm test"
|
|
41
40
|
},
|
|
42
41
|
"repository": {
|
|
43
42
|
"type": "git",
|