@ceralive/modem-control 1.1.0 → 1.2.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 +30 -6
- package/dist/backend/at-lease.d.ts +3 -1
- package/dist/backend/at-lease.js +11 -2
- package/dist/backend/recovery-ladder.d.ts +1 -1
- package/dist/backend/transition-preconditions.d.ts +35 -10
- package/dist/backend/transition-preconditions.js +78 -10
- package/dist/backend/usb-mode-transition.js +39 -19
- package/dist/capability/five-g-preference.d.ts +2 -2
- package/dist/capability/support-claim.d.ts +2 -2
- package/dist/domain/shadow-divergence.d.ts +1 -1
- package/dist/fcc/coverage.d.ts +20 -20
- package/dist/hardware/router-parsers.d.ts +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/journal/legacy-ceraui.d.ts +2 -2
- package/dist/observations/metric.d.ts +15 -1
- package/dist/observations/provenance.d.ts +2 -2
- package/dist/observations/reading.d.ts +1 -1
- package/dist/observations/state-separation.d.ts +1 -1
- package/dist/operation-ids.d.ts +2 -0
- package/dist/operation-ids.js +26 -0
- package/dist/ports/location.d.ts +1 -1
- package/dist/providers/contracts.d.ts +2 -2
- package/dist/providers/huawei-hilink/provider.d.ts +12 -12
- package/dist/providers/huawei-hilink/runtime.d.ts +2 -2
- package/dist/providers/modem-manager/errors.d.ts +1 -1
- package/dist/providers/modem-manager/generic-operations.d.ts +3 -1
- package/dist/providers/modem-manager/generic-operations.js +3 -1
- package/dist/providers/modem-manager/index.d.ts +1 -0
- package/dist/providers/modem-manager/index.js +1 -0
- package/dist/providers/modem-manager/provider.d.ts +2 -0
- package/dist/providers/modem-manager/provider.js +3 -0
- package/dist/providers/modem-manager/runtime-composition-operation.d.ts +32 -0
- package/dist/providers/modem-manager/runtime-composition-operation.js +151 -0
- package/dist/providers/modem-manager/types.d.ts +2 -0
- package/dist/providers/network-manager/types.d.ts +11 -3
- package/dist/providers/ufi-himi/operations.d.ts +1 -1
- package/dist/providers/ufi-himi/prohibitions.d.ts +22 -22
- package/dist/providers/ufi-himi/provider.d.ts +4 -4
- package/dist/providers/ufi-himi/qualcomm-evidence.d.ts +1 -1
- package/dist/providers/ufi-himi/transport.d.ts +2 -2
- package/dist/providers/zte-goform/provider.d.ts +18 -13
- package/dist/providers/zte-goform/provider.js +16 -16
- package/dist/providers/zte-goform/session.d.ts +8 -2
- package/dist/providers/zte-goform/session.js +95 -31
- package/dist/radio/mode-combinations.d.ts +11 -1
- package/dist/safety/flock-resource-ownership.js +18 -31
- package/dist/usb-mode/catalog-schema.d.ts +18 -18
- package/dist/usb-mode/index.d.ts +1 -0
- package/dist/usb-mode/index.js +1 -0
- package/dist/usb-mode/ingestion.d.ts +4 -4
- package/dist/usb-mode/runtime-capability.d.ts +59 -0
- package/dist/usb-mode/runtime-capability.js +157 -0
- package/dist/ussd/refusal.d.ts +1 -1
- package/dist/ussd/session.d.ts +24 -2
- package/package.json +1 -1
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
export const RUNTIME_COMPOSITION_VENDORS = ['fibocom', 'quectel', 'simcom', 'sierra'];
|
|
2
|
+
/** Commands only: selecting a port, sending, deadlines, and retries belong to the provider. */
|
|
3
|
+
export const RUNTIME_COMPOSITION_QUERY_REGISTRY = Object.freeze({
|
|
4
|
+
fibocom: Object.freeze({ current: 'AT+GTUSBMODE?', enumerate: 'AT+GTUSBMODE=?' }),
|
|
5
|
+
quectel: Object.freeze({ current: 'AT+QCFG="usbnet"', enumerate: 'AT+QCFG=?' }),
|
|
6
|
+
simcom: Object.freeze({ current: 'AT+CUSBPIDSWITCH?', enumerate: 'AT+CUSBPIDSWITCH=?' }),
|
|
7
|
+
sierra: Object.freeze({ current: 'AT!USBCOMP?', enumerate: 'AT!USBCOMP=?' }),
|
|
8
|
+
});
|
|
9
|
+
function decimalSetCommand(prefix) {
|
|
10
|
+
return (target) => typeof target === 'number' && Number.isSafeInteger(target) && target >= 0
|
|
11
|
+
? `${prefix}${target}`
|
|
12
|
+
: undefined;
|
|
13
|
+
}
|
|
14
|
+
/** Exact reviewed SET forms. Callers still allowlist only the one enumerated target selected. */
|
|
15
|
+
export const RUNTIME_COMPOSITION_SET_REGISTRY = Object.freeze({
|
|
16
|
+
fibocom: decimalSetCommand('AT+GTUSBMODE='),
|
|
17
|
+
quectel: decimalSetCommand('AT+QCFG="usbnet",'),
|
|
18
|
+
simcom: (target) => typeof target === 'string' && /^[0-9A-Fa-f]{4}$/.test(target)
|
|
19
|
+
? `AT+CUSBPIDSWITCH=${target.toUpperCase()},1,1`
|
|
20
|
+
: undefined,
|
|
21
|
+
sierra: decimalSetCommand('AT!USBCOMP='),
|
|
22
|
+
});
|
|
23
|
+
const UNKNOWN_VENDOR = Object.freeze({
|
|
24
|
+
status: 'unknown',
|
|
25
|
+
current: null,
|
|
26
|
+
enumerated: [],
|
|
27
|
+
returnPathProven: false,
|
|
28
|
+
offerable: [],
|
|
29
|
+
reason: 'vendor-unsupported',
|
|
30
|
+
});
|
|
31
|
+
const MALFORMED_RESPONSE = Object.freeze({
|
|
32
|
+
status: 'unknown',
|
|
33
|
+
current: null,
|
|
34
|
+
enumerated: [],
|
|
35
|
+
returnPathProven: false,
|
|
36
|
+
offerable: [],
|
|
37
|
+
reason: 'malformed-response',
|
|
38
|
+
});
|
|
39
|
+
function parseDecimalDomain(domain) {
|
|
40
|
+
const values = [];
|
|
41
|
+
for (const member of domain.split(',')) {
|
|
42
|
+
const token = member.trim();
|
|
43
|
+
const range = /^(\d+)-(\d+)$/.exec(token);
|
|
44
|
+
if (range !== null) {
|
|
45
|
+
const start = Number(range[1]);
|
|
46
|
+
const end = Number(range[2]);
|
|
47
|
+
if (!Number.isSafeInteger(start) ||
|
|
48
|
+
!Number.isSafeInteger(end) ||
|
|
49
|
+
start > end ||
|
|
50
|
+
end - start > 255)
|
|
51
|
+
return undefined;
|
|
52
|
+
for (let value = start; value <= end; value += 1)
|
|
53
|
+
values.push(value);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (!/^\d+$/.test(token))
|
|
57
|
+
return undefined;
|
|
58
|
+
const value = Number(token);
|
|
59
|
+
if (!Number.isSafeInteger(value))
|
|
60
|
+
return undefined;
|
|
61
|
+
values.push(value);
|
|
62
|
+
}
|
|
63
|
+
return values.length > 0 && new Set(values).size === values.length ? values : undefined;
|
|
64
|
+
}
|
|
65
|
+
function parseFibocom(input) {
|
|
66
|
+
const current = /^\s*\+GTUSBMODE:\s*(\d+)\s*$/m.exec(input.currentResponse);
|
|
67
|
+
const enumeration = /^\s*\+GTUSBMODE:\s*\(([^)]+)\)\s*$/m.exec(input.enumerationResponse);
|
|
68
|
+
if (current === null || enumeration === null)
|
|
69
|
+
return undefined;
|
|
70
|
+
const enumerated = parseDecimalDomain(enumeration[1] ?? '');
|
|
71
|
+
return enumerated === undefined ? undefined : { current: Number(current[1]), enumerated };
|
|
72
|
+
}
|
|
73
|
+
function parseQuectel(input) {
|
|
74
|
+
const current = /^\s*\+QCFG:\s*"usbnet"\s*,\s*(\d+)\s*$/m.exec(input.currentResponse);
|
|
75
|
+
const enumeration = /^\s*\+QCFG:\s*"usbnet"\s*,\s*\(([^)]+)\)\s*$/m.exec(input.enumerationResponse);
|
|
76
|
+
if (current === null || enumeration === null)
|
|
77
|
+
return undefined;
|
|
78
|
+
const enumerated = parseDecimalDomain(enumeration[1] ?? '');
|
|
79
|
+
return enumerated === undefined ? undefined : { current: Number(current[1]), enumerated };
|
|
80
|
+
}
|
|
81
|
+
function parseSimcom(input) {
|
|
82
|
+
const current = /^\s*\+CUSBPIDSWITCH:\s*([0-9A-Fa-f]{4})\s*$/m.exec(input.currentResponse);
|
|
83
|
+
const enumeration = /\+CUSBPIDSWITCH:\s*\(([^)]+)\)\s*,\s*\(0-1\)\s*,\s*\(0-1\)/m.exec(input.enumerationResponse);
|
|
84
|
+
if (current === null || enumeration === null)
|
|
85
|
+
return undefined;
|
|
86
|
+
const tokens = (enumeration[1] ?? '').split(',').map((token) => token.trim().toUpperCase());
|
|
87
|
+
if (tokens.length === 0 || tokens.some((token) => !/^[0-9A-F]{4}$/.test(token)))
|
|
88
|
+
return undefined;
|
|
89
|
+
if (new Set(tokens).size !== tokens.length)
|
|
90
|
+
return undefined;
|
|
91
|
+
return { current: (current[1] ?? '').toUpperCase(), enumerated: tokens };
|
|
92
|
+
}
|
|
93
|
+
function parseSierra(input) {
|
|
94
|
+
const current = /^\s*!USBCOMP:\s*(\d+)(?:\s*,.*)?$/m.exec(input.currentResponse);
|
|
95
|
+
if (current === null)
|
|
96
|
+
return undefined;
|
|
97
|
+
const enumerated = Array.from(input.enumerationResponse.matchAll(/^\s*(\d+)\s*:\s*.+$/gm), (match) => Number(match[1]));
|
|
98
|
+
if (enumerated.length === 0 || new Set(enumerated).size !== enumerated.length)
|
|
99
|
+
return undefined;
|
|
100
|
+
return { current: Number(current[1]), enumerated };
|
|
101
|
+
}
|
|
102
|
+
const PARSERS = {
|
|
103
|
+
fibocom: parseFibocom,
|
|
104
|
+
quectel: parseQuectel,
|
|
105
|
+
simcom: parseSimcom,
|
|
106
|
+
sierra: parseSierra,
|
|
107
|
+
};
|
|
108
|
+
export function isRuntimeCompositionVendor(vendor) {
|
|
109
|
+
return Object.hasOwn(RUNTIME_COMPOSITION_QUERY_REGISTRY, vendor);
|
|
110
|
+
}
|
|
111
|
+
export function buildRuntimeCompositionSetCommand(vendor, target) {
|
|
112
|
+
const normalized = vendor.trim().toLowerCase();
|
|
113
|
+
return isRuntimeCompositionVendor(normalized)
|
|
114
|
+
? RUNTIME_COMPOSITION_SET_REGISTRY[normalized](target)
|
|
115
|
+
: undefined;
|
|
116
|
+
}
|
|
117
|
+
/** Parse only the vendor READ response used by the weaker post-switch proof tier. */
|
|
118
|
+
export function readRuntimeCompositionCurrent(vendor, response) {
|
|
119
|
+
const normalized = vendor.trim().toLowerCase();
|
|
120
|
+
if (!isRuntimeCompositionVendor(normalized))
|
|
121
|
+
return undefined;
|
|
122
|
+
switch (normalized) {
|
|
123
|
+
case 'fibocom': {
|
|
124
|
+
const match = /^\s*\+GTUSBMODE:\s*(\d+)\s*$/m.exec(response);
|
|
125
|
+
return match === null ? undefined : Number(match[1]);
|
|
126
|
+
}
|
|
127
|
+
case 'quectel': {
|
|
128
|
+
const match = /^\s*\+QCFG:\s*"usbnet"\s*,\s*(\d+)\s*$/m.exec(response);
|
|
129
|
+
return match === null ? undefined : Number(match[1]);
|
|
130
|
+
}
|
|
131
|
+
case 'simcom': {
|
|
132
|
+
const match = /^\s*\+CUSBPIDSWITCH:\s*([0-9A-Fa-f]{4})\s*$/m.exec(response);
|
|
133
|
+
return match === null ? undefined : (match[1] ?? '').toUpperCase();
|
|
134
|
+
}
|
|
135
|
+
case 'sierra': {
|
|
136
|
+
const match = /^\s*!USBCOMP:\s*(\d+)(?:\s*,.*)?$/m.exec(response);
|
|
137
|
+
return match === null ? undefined : Number(match[1]);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/** Derive controls exclusively from the device's current and enumerated response text. */
|
|
142
|
+
export function resolveRuntimeCompositionCapability(input) {
|
|
143
|
+
const vendor = input.vendor.trim().toLowerCase();
|
|
144
|
+
if (!isRuntimeCompositionVendor(vendor))
|
|
145
|
+
return UNKNOWN_VENDOR;
|
|
146
|
+
const parsed = PARSERS[vendor](input);
|
|
147
|
+
if (parsed === undefined)
|
|
148
|
+
return MALFORMED_RESPONSE;
|
|
149
|
+
const returnPathProven = parsed.enumerated.includes(parsed.current);
|
|
150
|
+
return {
|
|
151
|
+
status: 'available',
|
|
152
|
+
current: parsed.current,
|
|
153
|
+
enumerated: parsed.enumerated,
|
|
154
|
+
returnPathProven,
|
|
155
|
+
offerable: returnPathProven ? parsed.enumerated : [],
|
|
156
|
+
};
|
|
157
|
+
}
|
package/dist/ussd/refusal.d.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* timeout — the bounded wait elapsed with no answer.
|
|
13
13
|
* transport-failed — the bus call itself failed; the modem never answered.
|
|
14
14
|
*/
|
|
15
|
-
export declare const USSD_REFUSAL_REASONS: readonly [
|
|
15
|
+
export declare const USSD_REFUSAL_REASONS: readonly ['unsupported', 'lte-only-unsupported', 'carrier-rejected', 'not-registered', 'session-busy', 'no-session', 'invalid-state', 'timeout', 'transport-failed'];
|
|
16
16
|
export type UssdRefusalReason = (typeof USSD_REFUSAL_REASONS)[number];
|
|
17
17
|
/**
|
|
18
18
|
* What the modem is registered on, as far as anyone has looked.
|
package/dist/ussd/session.d.ts
CHANGED
|
@@ -8,10 +8,32 @@ import type { UssdRefusalReason } from './refusal.js';
|
|
|
8
8
|
* them a second `initiate` racing the first would be judged against `idle` and
|
|
9
9
|
* allowed through, which is exactly the double-open the network answers busy.
|
|
10
10
|
*/
|
|
11
|
-
export declare const USSD_SESSION_STATES: readonly [
|
|
11
|
+
export declare const USSD_SESSION_STATES: readonly [
|
|
12
|
+
/** No session. MM `IDLE`. */
|
|
13
|
+
'idle',
|
|
14
|
+
/** `Initiate` dispatched, reply outstanding. Local. */
|
|
15
|
+
'initiating',
|
|
16
|
+
/** Network answered and the session is open with nothing pending. MM `ACTIVE`. */
|
|
17
|
+
'active',
|
|
18
|
+
/** Network asked a question; a `Respond` is required. MM `USER_RESPONSE`. */
|
|
19
|
+
'awaiting-reply',
|
|
20
|
+
/** `Respond` dispatched, reply outstanding. Local. */
|
|
21
|
+
'responding',
|
|
22
|
+
/** `Cancel` dispatched, confirmation outstanding. Local. */
|
|
23
|
+
'cancelling',
|
|
24
|
+
/** Terminal for this session object. A new session starts from a new machine. */
|
|
25
|
+
'closed'];
|
|
12
26
|
export type UssdSessionState = (typeof USSD_SESSION_STATES)[number];
|
|
13
27
|
/** How a session that reached `closed` got there. */
|
|
14
|
-
export declare const USSD_SESSION_OUTCOMES: readonly [
|
|
28
|
+
export declare const USSD_SESSION_OUTCOMES: readonly [
|
|
29
|
+
/** The network completed the dialogue and released the session. */
|
|
30
|
+
'completed',
|
|
31
|
+
/** The operator cancelled it. */
|
|
32
|
+
'cancelled',
|
|
33
|
+
/** No answer within the bound; the machine closed it locally. */
|
|
34
|
+
'timed-out',
|
|
35
|
+
/** The network or the modem refused. `refusal` names which. */
|
|
36
|
+
'failed'];
|
|
15
37
|
export type UssdSessionOutcome = (typeof USSD_SESSION_OUTCOMES)[number];
|
|
16
38
|
/** MM's post-call session state, decoded. */
|
|
17
39
|
export type UssdRepliedState = 'awaiting-reply' | 'active' | 'released';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ceralive/modem-control",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Cellular modem control for CeraLive — ModemManager D-Bus backend, NetworkManager adapter, desired-state reconciler, USB composition-mode model, data-usage sampler.",
|
|
6
6
|
"license": "AGPL-3.0",
|