@ceralive/modem-control 0.2.0 → 1.1.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 +317 -0
- package/dist/backend/at-lease.d.ts +57 -0
- package/dist/backend/at-lease.js +109 -0
- package/dist/backend/cell-info.d.ts +46 -0
- package/dist/backend/cell-info.js +124 -0
- package/dist/backend/constants.d.ts +24 -0
- package/{src/backend/constants.ts → dist/backend/constants.js} +4 -9
- package/dist/backend/device-classifier.d.ts +46 -0
- package/dist/backend/device-classifier.js +258 -0
- package/dist/backend/enrichment.d.ts +28 -0
- package/dist/backend/enrichment.js +59 -0
- package/dist/backend/features.d.ts +61 -0
- package/dist/backend/features.js +109 -0
- package/dist/backend/identity-ladder.d.ts +57 -0
- package/dist/backend/identity-ladder.js +158 -0
- package/dist/backend/identity-registry.d.ts +51 -0
- package/dist/backend/identity-registry.js +101 -0
- package/dist/backend/index.d.ts +30 -0
- package/dist/backend/index.js +35 -0
- package/dist/backend/lifecycle-interlock.d.ts +25 -0
- package/dist/backend/lifecycle-interlock.js +18 -0
- package/dist/backend/managed-objects.d.ts +39 -0
- package/dist/backend/managed-objects.js +82 -0
- package/dist/backend/mapping.d.ts +11 -0
- package/dist/backend/mapping.js +148 -0
- package/dist/backend/mm-backend.d.ts +44 -0
- package/dist/backend/mm-backend.js +154 -0
- package/dist/backend/mm-location.d.ts +21 -0
- package/dist/backend/mm-location.js +237 -0
- package/dist/backend/mm-mutations.d.ts +42 -0
- package/dist/backend/mm-mutations.js +259 -0
- package/dist/backend/modem-actor.d.ts +41 -0
- package/dist/backend/modem-actor.js +78 -0
- package/dist/backend/nm-auto-apn.d.ts +54 -0
- package/dist/backend/nm-auto-apn.js +124 -0
- package/dist/backend/nm-gsm-fields.d.ts +23 -0
- package/dist/backend/nm-gsm-fields.js +107 -0
- package/dist/backend/nmcli-nm-port.d.ts +28 -0
- package/dist/backend/nmcli-nm-port.js +173 -0
- package/dist/backend/nmcli-runner.d.ts +24 -0
- package/dist/backend/nmcli-runner.js +35 -0
- package/dist/backend/observer.d.ts +37 -0
- package/dist/backend/observer.js +219 -0
- package/dist/backend/power-contract.d.ts +49 -0
- package/dist/backend/power-contract.js +34 -0
- package/dist/backend/recovery-attribution.d.ts +32 -0
- package/dist/backend/recovery-attribution.js +57 -0
- package/dist/backend/recovery-budget.d.ts +45 -0
- package/dist/backend/recovery-budget.js +44 -0
- package/dist/backend/recovery-ladder.d.ts +94 -0
- package/dist/backend/recovery-ladder.js +116 -0
- package/dist/backend/router-ethernet.d.ts +19 -0
- package/dist/backend/router-ethernet.js +66 -0
- package/dist/backend/row-store.d.ts +13 -0
- package/dist/backend/row-store.js +82 -0
- package/dist/backend/signal-setup.d.ts +30 -0
- package/dist/backend/signal-setup.js +92 -0
- package/dist/backend/sim-unlock.d.ts +13 -0
- package/dist/backend/sim-unlock.js +153 -0
- package/dist/backend/transition-preconditions.d.ts +76 -0
- package/dist/backend/transition-preconditions.js +64 -0
- package/dist/backend/usage/accounting.d.ts +39 -0
- package/dist/backend/usage/accounting.js +73 -0
- package/dist/backend/usage/billing-cycle.d.ts +11 -0
- package/dist/backend/usage/billing-cycle.js +39 -0
- package/dist/backend/usage/boot-id.d.ts +6 -0
- package/{src/backend/usage/boot-id.ts → dist/backend/usage/boot-id.js} +7 -7
- package/dist/backend/usage/index.d.ts +8 -0
- package/dist/backend/usage/index.js +11 -0
- package/dist/backend/usage/policy-store.d.ts +50 -0
- package/dist/backend/usage/policy-store.js +161 -0
- package/dist/backend/usage/policy-write.d.ts +65 -0
- package/dist/backend/usage/policy-write.js +112 -0
- package/dist/backend/usage/proc-net-dev.d.ts +18 -0
- package/{src/backend/usage/proc-net-dev.ts → dist/backend/usage/proc-net-dev.js} +39 -47
- package/dist/backend/usage/sampler.d.ts +75 -0
- package/dist/backend/usage/sampler.js +211 -0
- package/dist/backend/usage/store.d.ts +38 -0
- package/dist/backend/usage/store.js +126 -0
- package/dist/backend/usb-device-snapshot.d.ts +31 -0
- package/dist/backend/usb-device-snapshot.js +1 -0
- package/dist/backend/usb-enumerator.d.ts +21 -0
- package/dist/backend/usb-enumerator.js +153 -0
- package/dist/backend/usb-mode-transition.d.ts +29 -0
- package/dist/backend/usb-mode-transition.js +196 -0
- package/dist/band/band-names.d.ts +42 -0
- package/dist/band/band-names.js +150 -0
- package/dist/band/certification.d.ts +84 -0
- package/dist/band/certification.js +127 -0
- package/dist/band/certified-bands.json +4 -0
- package/dist/band/index.d.ts +2 -0
- package/dist/band/index.js +8 -0
- package/dist/capability/detect.d.ts +52 -0
- package/dist/capability/detect.js +86 -0
- package/dist/capability/five-g-preference.d.ts +104 -0
- package/dist/capability/five-g-preference.js +171 -0
- package/dist/capability/index.d.ts +3 -0
- package/dist/capability/index.js +10 -0
- package/dist/capability/support-claim.d.ts +39 -0
- package/dist/capability/support-claim.js +81 -0
- package/dist/domain/brand.d.ts +10 -0
- package/dist/domain/brand.js +21 -0
- package/dist/domain/errors.d.ts +32 -0
- package/dist/domain/errors.js +48 -0
- package/dist/domain/generation.d.ts +8 -0
- package/dist/domain/generation.js +12 -0
- package/dist/domain/guards.d.ts +6 -0
- package/dist/domain/guards.js +127 -0
- package/dist/domain/identity.d.ts +109 -0
- package/dist/domain/identity.js +86 -0
- package/dist/domain/index.d.ts +14 -0
- package/dist/domain/index.js +18 -0
- package/dist/domain/mm-enums.d.ts +12 -0
- package/dist/domain/mm-enums.js +139 -0
- package/dist/domain/modem-presentation.d.ts +10 -0
- package/dist/domain/modem-presentation.js +39 -0
- package/dist/domain/observation.d.ts +39 -0
- package/dist/domain/observation.js +4 -0
- package/dist/domain/operation.d.ts +118 -0
- package/dist/domain/operation.js +85 -0
- package/dist/domain/physical-identity.d.ts +43 -0
- package/dist/domain/physical-identity.js +113 -0
- package/{src/domain/policy.ts → dist/domain/policy.d.ts} +34 -69
- package/dist/domain/policy.js +38 -0
- package/dist/domain/shadow-divergence.d.ts +27 -0
- package/dist/domain/shadow-divergence.js +70 -0
- package/dist/domain/snapshot.d.ts +53 -0
- package/dist/domain/snapshot.js +75 -0
- package/{src/domain/state.ts → dist/domain/state.d.ts} +23 -122
- package/dist/domain/state.js +38 -0
- package/dist/fcc/coverage.d.ts +63 -0
- package/dist/fcc/coverage.js +102 -0
- package/dist/fcc/index.d.ts +3 -0
- package/dist/fcc/index.js +12 -0
- package/dist/fcc/policy-store.d.ts +40 -0
- package/dist/fcc/policy-store.js +146 -0
- package/dist/fcc/policy-write.d.ts +32 -0
- package/dist/fcc/policy-write.js +37 -0
- package/dist/hardware/hilink-protocol.d.ts +39 -0
- package/dist/hardware/hilink-protocol.js +58 -0
- package/dist/hardware/index.d.ts +3 -0
- package/dist/hardware/index.js +15 -0
- package/dist/hardware/router-parsers.d.ts +89 -0
- package/dist/hardware/router-parsers.js +234 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +26 -0
- package/dist/journal/codec.d.ts +44 -0
- package/dist/journal/codec.js +198 -0
- package/dist/journal/engine.d.ts +28 -0
- package/dist/journal/engine.js +68 -0
- package/dist/journal/entry.d.ts +74 -0
- package/dist/journal/entry.js +56 -0
- package/dist/journal/index.d.ts +6 -0
- package/dist/journal/index.js +6 -0
- package/dist/journal/legacy-ceraui.d.ts +73 -0
- package/dist/journal/legacy-ceraui.js +227 -0
- package/dist/journal/recovery.d.ts +58 -0
- package/dist/journal/recovery.js +117 -0
- package/dist/journal/store.d.ts +55 -0
- package/dist/journal/store.js +150 -0
- package/dist/location/fix-state.d.ts +53 -0
- package/dist/location/fix-state.js +75 -0
- package/dist/location/index.d.ts +2 -0
- package/dist/location/index.js +8 -0
- package/dist/location/nmea.d.ts +10 -0
- package/dist/location/nmea.js +89 -0
- package/dist/observations/envelope.d.ts +63 -0
- package/dist/observations/envelope.js +77 -0
- package/dist/observations/freshness.d.ts +28 -0
- package/dist/observations/freshness.js +76 -0
- package/dist/observations/index.d.ts +13 -0
- package/dist/observations/index.js +24 -0
- package/dist/observations/metric.d.ts +47 -0
- package/dist/observations/metric.js +73 -0
- package/dist/observations/model.d.ts +80 -0
- package/dist/observations/model.js +11 -0
- package/dist/observations/provenance.d.ts +94 -0
- package/dist/observations/provenance.js +67 -0
- package/dist/observations/raw.d.ts +42 -0
- package/dist/observations/raw.js +146 -0
- package/dist/observations/reading.d.ts +51 -0
- package/dist/observations/reading.js +65 -0
- package/dist/observations/sources/hilink.d.ts +10 -0
- package/dist/observations/sources/hilink.js +84 -0
- package/dist/observations/sources/modemmanager.d.ts +18 -0
- package/dist/observations/sources/modemmanager.js +239 -0
- package/dist/observations/sources/router-shared.d.ts +29 -0
- package/dist/observations/sources/router-shared.js +68 -0
- package/dist/observations/sources/ufi.d.ts +10 -0
- package/dist/observations/sources/ufi.js +111 -0
- package/dist/observations/sources/zte.d.ts +7 -0
- package/dist/observations/sources/zte.js +71 -0
- package/dist/observations/state-separation.d.ts +64 -0
- package/dist/observations/state-separation.js +52 -0
- package/dist/operations/contracts.d.ts +72 -0
- package/dist/operations/contracts.js +1 -0
- package/dist/operations/index.d.ts +1 -0
- package/dist/operations/index.js +1 -0
- package/dist/operations/operation-engine.d.ts +16 -0
- package/dist/operations/operation-engine.js +194 -0
- package/dist/ports/index.d.ts +12 -0
- package/{src/ports/index.ts → dist/ports/index.js} +12 -8
- package/dist/ports/location.d.ts +87 -0
- package/dist/ports/location.js +36 -0
- package/dist/ports/modem-manager.d.ts +89 -0
- package/dist/ports/modem-manager.js +9 -0
- package/dist/ports/mutation-admission.d.ts +27 -0
- package/dist/ports/mutation-admission.js +9 -0
- package/dist/ports/network-manager.d.ts +68 -0
- package/dist/ports/network-manager.js +13 -0
- package/{src/ports/observation.ts → dist/ports/observation.d.ts} +16 -27
- package/dist/ports/observation.js +7 -0
- package/dist/ports/ops.d.ts +44 -0
- package/dist/ports/ops.js +16 -0
- package/{src/ports/receipts.ts → dist/ports/receipts.d.ts} +5 -27
- package/dist/ports/receipts.js +9 -0
- package/dist/ports/reconcile.d.ts +33 -0
- package/dist/ports/reconcile.js +200 -0
- package/dist/ports/resource-ownership.d.ts +29 -0
- package/dist/ports/resource-ownership.js +1 -0
- package/dist/ports/router.d.ts +19 -0
- package/dist/ports/router.js +7 -0
- package/dist/ports/sms.d.ts +64 -0
- package/dist/ports/sms.js +24 -0
- package/dist/ports/uhubctl.d.ts +6 -0
- package/dist/ports/uhubctl.js +1 -0
- package/dist/providers/contracts.d.ts +124 -0
- package/dist/providers/contracts.js +10 -0
- package/dist/providers/huawei-hilink/index.d.ts +2 -0
- package/dist/providers/huawei-hilink/index.js +2 -0
- package/dist/providers/huawei-hilink/operations.d.ts +20 -0
- package/dist/providers/huawei-hilink/operations.js +56 -0
- package/dist/providers/huawei-hilink/provider.d.ts +52 -0
- package/dist/providers/huawei-hilink/provider.js +76 -0
- package/dist/providers/huawei-hilink/runtime.d.ts +22 -0
- package/dist/providers/huawei-hilink/runtime.js +171 -0
- package/dist/providers/huawei-hilink/session.d.ts +28 -0
- package/dist/providers/huawei-hilink/session.js +120 -0
- package/dist/providers/huawei-hilink/transport.d.ts +19 -0
- package/dist/providers/huawei-hilink/transport.js +1 -0
- package/dist/providers/index.d.ts +8 -0
- package/dist/providers/index.js +8 -0
- package/dist/providers/matcher.d.ts +3 -0
- package/dist/providers/matcher.js +205 -0
- package/dist/providers/modem-manager/errors.d.ts +7 -0
- package/dist/providers/modem-manager/errors.js +37 -0
- package/dist/providers/modem-manager/generic-operations.d.ts +8 -0
- package/dist/providers/modem-manager/generic-operations.js +207 -0
- package/dist/providers/modem-manager/index.d.ts +3 -0
- package/dist/providers/modem-manager/index.js +3 -0
- package/dist/providers/modem-manager/module-operations.d.ts +21 -0
- package/dist/providers/modem-manager/module-operations.js +118 -0
- package/dist/providers/modem-manager/provider.d.ts +39 -0
- package/dist/providers/modem-manager/provider.js +152 -0
- package/dist/providers/modem-manager/snapshot.d.ts +5 -0
- package/dist/providers/modem-manager/snapshot.js +204 -0
- package/dist/providers/modem-manager/types.d.ts +135 -0
- package/dist/providers/modem-manager/types.js +1 -0
- package/dist/providers/network-manager/adapter.d.ts +71 -0
- package/dist/providers/network-manager/adapter.js +348 -0
- package/dist/providers/network-manager/index.d.ts +2 -0
- package/dist/providers/network-manager/index.js +2 -0
- package/dist/providers/network-manager/types.d.ts +163 -0
- package/dist/providers/network-manager/types.js +77 -0
- package/dist/providers/registry.d.ts +13 -0
- package/dist/providers/registry.js +33 -0
- package/dist/providers/ufi-himi/index.d.ts +6 -0
- package/dist/providers/ufi-himi/index.js +6 -0
- package/dist/providers/ufi-himi/operations.d.ts +41 -0
- package/dist/providers/ufi-himi/operations.js +66 -0
- package/dist/providers/ufi-himi/prohibitions.d.ts +62 -0
- package/dist/providers/ufi-himi/prohibitions.js +88 -0
- package/dist/providers/ufi-himi/provider.d.ts +41 -0
- package/dist/providers/ufi-himi/provider.js +204 -0
- package/dist/providers/ufi-himi/qualcomm-evidence.d.ts +32 -0
- package/dist/providers/ufi-himi/qualcomm-evidence.js +51 -0
- package/dist/providers/ufi-himi/session.d.ts +46 -0
- package/dist/providers/ufi-himi/session.js +92 -0
- package/dist/providers/ufi-himi/transport.d.ts +29 -0
- package/dist/providers/ufi-himi/transport.js +25 -0
- package/dist/providers/zte-goform/index.d.ts +2 -0
- package/dist/providers/zte-goform/index.js +2 -0
- package/dist/providers/zte-goform/provider.d.ts +51 -0
- package/dist/providers/zte-goform/provider.js +101 -0
- package/dist/providers/zte-goform/session.d.ts +17 -0
- package/dist/providers/zte-goform/session.js +133 -0
- package/dist/providers/zte-goform/transport.d.ts +16 -0
- package/dist/providers/zte-goform/transport.js +1 -0
- package/dist/radio/band-truth.d.ts +50 -0
- package/dist/radio/band-truth.js +92 -0
- package/dist/radio/index.d.ts +3 -0
- package/dist/radio/index.js +10 -0
- package/dist/radio/mode-combinations.d.ts +78 -0
- package/dist/radio/mode-combinations.js +198 -0
- package/dist/radio/mode-truth.d.ts +67 -0
- package/dist/radio/mode-truth.js +112 -0
- package/dist/redact.d.ts +15 -0
- package/dist/redact.js +189 -0
- package/dist/safety/composition-root.d.ts +28 -0
- package/dist/safety/composition-root.js +65 -0
- package/dist/safety/flock-resource-ownership.d.ts +11 -0
- package/dist/safety/flock-resource-ownership.js +151 -0
- package/dist/safety/index.d.ts +2 -0
- package/dist/safety/index.js +2 -0
- package/dist/sms/dbus-messaging.d.ts +17 -0
- package/dist/sms/dbus-messaging.js +185 -0
- package/dist/sms/inbox-store.d.ts +10 -0
- package/dist/sms/inbox-store.js +82 -0
- package/dist/sms/index.d.ts +4 -0
- package/dist/sms/index.js +10 -0
- package/dist/sms/mmcli-parse.d.ts +54 -0
- package/dist/sms/mmcli-parse.js +224 -0
- package/dist/sms/normalize.d.ts +42 -0
- package/dist/sms/normalize.js +95 -0
- package/dist/testing/domain-fakes.d.ts +58 -0
- package/dist/testing/domain-fakes.js +98 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +17 -0
- package/dist/testing/provider-fakes.d.ts +45 -0
- package/dist/testing/provider-fakes.js +64 -0
- package/dist/transport/calls.d.ts +9 -0
- package/dist/transport/calls.js +88 -0
- package/dist/transport/codec.d.ts +3 -0
- package/dist/transport/codec.js +207 -0
- package/dist/transport/dbus-native.d.ts +57 -0
- package/dist/transport/dbus-native.js +17 -0
- package/dist/transport/errors.d.ts +21 -0
- package/{src/transport/errors.ts → dist/transport/errors.js} +34 -46
- package/dist/transport/index.d.ts +4 -0
- package/dist/transport/index.js +9 -0
- package/dist/transport/signals.d.ts +15 -0
- package/dist/transport/signals.js +123 -0
- package/dist/transport/signature.d.ts +7 -0
- package/dist/transport/signature.js +94 -0
- package/dist/transport/transport.d.ts +2 -0
- package/dist/transport/transport.js +202 -0
- package/dist/transport/types.d.ts +61 -0
- package/dist/transport/types.js +19 -0
- package/dist/usb-mode/catalog-schema.d.ts +139 -0
- package/dist/usb-mode/catalog-schema.js +97 -0
- package/dist/usb-mode/catalog.d.ts +21 -0
- package/{src/usb-mode/catalog.ts → dist/usb-mode/catalog.js} +10 -32
- package/dist/usb-mode/certified-catalog.json +67 -0
- package/dist/usb-mode/index.d.ts +5 -0
- package/dist/usb-mode/index.js +15 -0
- package/dist/usb-mode/ingestion.d.ts +111 -0
- package/dist/usb-mode/ingestion.js +187 -0
- package/dist/usb-mode/promotion-review.d.ts +21 -0
- package/dist/usb-mode/promotion-review.js +87 -0
- package/dist/usb-mode/usb-devices-parse.d.ts +36 -0
- package/dist/usb-mode/usb-devices-parse.js +157 -0
- package/dist/ussd/calls.d.ts +32 -0
- package/dist/ussd/calls.js +96 -0
- package/dist/ussd/index.d.ts +5 -0
- package/dist/ussd/index.js +12 -0
- package/dist/ussd/mm-ussd.d.ts +37 -0
- package/dist/ussd/mm-ussd.js +205 -0
- package/dist/ussd/refusal.d.ts +53 -0
- package/dist/ussd/refusal.js +154 -0
- package/dist/ussd/registration.d.ts +20 -0
- package/dist/ussd/registration.js +101 -0
- package/dist/ussd/session.d.ts +84 -0
- package/dist/ussd/session.js +163 -0
- package/package.json +38 -4
- package/src/backend/at-lease.test.ts +0 -106
- package/src/backend/at-lease.ts +0 -158
- package/src/backend/cell-info.test.ts +0 -154
- package/src/backend/cell-info.ts +0 -160
- package/src/backend/device-classifier.test.ts +0 -168
- package/src/backend/device-classifier.ts +0 -248
- package/src/backend/enrichment.ts +0 -96
- package/src/backend/features.test.ts +0 -162
- package/src/backend/features.ts +0 -179
- package/src/backend/identity-ladder.test.ts +0 -117
- package/src/backend/identity-ladder.ts +0 -221
- package/src/backend/identity-registry.test.ts +0 -89
- package/src/backend/identity-registry.ts +0 -151
- package/src/backend/index.ts +0 -221
- package/src/backend/lifecycle-interlock.ts +0 -38
- package/src/backend/managed-objects.ts +0 -108
- package/src/backend/mapping.ts +0 -160
- package/src/backend/mm-backend.ts +0 -191
- package/src/backend/mm-mutations.ts +0 -228
- package/src/backend/modem-actor.test.ts +0 -95
- package/src/backend/modem-actor.ts +0 -112
- package/src/backend/nm-auto-apn.ts +0 -161
- package/src/backend/nm-gsm-fields.ts +0 -122
- package/src/backend/nmcli-nm-port.ts +0 -228
- package/src/backend/nmcli-runner.ts +0 -52
- package/src/backend/observer.ts +0 -297
- package/src/backend/power-contract.test.ts +0 -40
- package/src/backend/power-contract.ts +0 -83
- package/src/backend/recovery-attribution.test.ts +0 -102
- package/src/backend/recovery-attribution.ts +0 -86
- package/src/backend/recovery-budget.test.ts +0 -64
- package/src/backend/recovery-budget.ts +0 -84
- package/src/backend/recovery-ladder.test.ts +0 -257
- package/src/backend/recovery-ladder.ts +0 -249
- package/src/backend/router-ethernet.test.ts +0 -71
- package/src/backend/router-ethernet.ts +0 -90
- package/src/backend/row-store.ts +0 -105
- package/src/backend/signal-setup.ts +0 -112
- package/src/backend/sim-unlock.ts +0 -193
- package/src/backend/transition-preconditions.ts +0 -149
- package/src/backend/usage/accounting.test.ts +0 -147
- package/src/backend/usage/accounting.ts +0 -123
- package/src/backend/usage/billing-cycle.test.ts +0 -62
- package/src/backend/usage/billing-cycle.ts +0 -45
- package/src/backend/usage/index.ts +0 -37
- package/src/backend/usage/proc-net-dev.test.ts +0 -56
- package/src/backend/usage/sampler.test.ts +0 -219
- package/src/backend/usage/sampler.ts +0 -228
- package/src/backend/usage/store.test.ts +0 -148
- package/src/backend/usage/store.ts +0 -177
- package/src/backend/usb-enumerator.test.ts +0 -87
- package/src/backend/usb-enumerator.ts +0 -181
- package/src/backend/usb-mode-transition.test.ts +0 -323
- package/src/backend/usb-mode-transition.ts +0 -253
- package/src/domain/brand.ts +0 -29
- package/src/domain/errors.ts +0 -77
- package/src/domain/guards.test.ts +0 -218
- package/src/domain/guards.ts +0 -144
- package/src/domain/identity.test.ts +0 -83
- package/src/domain/identity.ts +0 -165
- package/src/domain/index.ts +0 -12
- package/src/domain/snapshot.test.ts +0 -266
- package/src/domain/snapshot.ts +0 -120
- package/src/index.test.ts +0 -6
- package/src/index.ts +0 -15
- package/src/ports/README.md +0 -61
- package/src/ports/forbidden-surface.test.ts +0 -241
- package/src/ports/modem-manager.ts +0 -72
- package/src/ports/network-manager.ts +0 -87
- package/src/ports/ops.ts +0 -60
- package/src/ports/ops.type-test.ts +0 -39
- package/src/ports/receipts.test.ts +0 -153
- package/src/ports/reconcile.test.ts +0 -152
- package/src/ports/reconcile.ts +0 -338
- package/src/ports/router.ts +0 -29
- package/src/redact.test.ts +0 -82
- package/src/redact.ts +0 -73
- package/src/transport/README.md +0 -65
- package/src/transport/calls.ts +0 -113
- package/src/transport/characterization.test.ts +0 -260
- package/src/transport/codec.test.ts +0 -118
- package/src/transport/codec.ts +0 -240
- package/src/transport/conformance-python.test.ts +0 -152
- package/src/transport/conformance-same-lib.test.ts +0 -115
- package/src/transport/dbus-native-lib.d.ts +0 -19
- package/src/transport/dbus-native.ts +0 -85
- package/src/transport/index.ts +0 -30
- package/src/transport/no-library-leak.test.ts +0 -61
- package/src/transport/reliability.test.ts +0 -173
- package/src/transport/signals.ts +0 -150
- package/src/transport/signature.ts +0 -110
- package/src/transport/test-support/fake-service.ts +0 -168
- package/src/transport/test-support/independent-producer.py +0 -110
- package/src/transport/test-support/private-bus.ts +0 -66
- package/src/transport/transport.ts +0 -250
- package/src/transport/types.ts +0 -118
- package/src/usb-mode/catalog-schema.test.ts +0 -181
- package/src/usb-mode/catalog-schema.ts +0 -113
- package/src/usb-mode/certified-catalog.json +0 -67
- package/src/usb-mode/index.ts +0 -27
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// Pure mapping: a decoded ModemManager object tree → a `CellularSnapshot`.
|
|
2
|
+
//
|
|
3
|
+
// A3.1 is lifecycle-only. This mapper is DELIBERATELY conservative: it captures
|
|
4
|
+
// identity, presence, source health, radio power, and MM state — the facts the
|
|
5
|
+
// observer needs to order and reconcile modems — but leaves the richer 3GPP
|
|
6
|
+
// registration + access-technology set as `unknown`/empty. Faithful registration and
|
|
7
|
+
// cell-info normalisation are A3.2 (identity ladder) and A3.3 (Signal.Setup / cell
|
|
8
|
+
// info); mapping them here would either be a guess or trip the domain guards. The
|
|
9
|
+
// snapshot this produces is always guard-valid by construction.
|
|
10
|
+
import { imeiEquipmentId, subscriberNumber as makeSubscriberNumber, subscriptionId as makeSubscriptionId, runtimePath, } from '../domain/index.js';
|
|
11
|
+
import { MODEM_IFACE, MODEM3GPP_IFACE, SIM_IFACE } from './constants.js';
|
|
12
|
+
import { findInterface, followObjectPath, numberProp, pathsWithInterface, stringArrayProp, stringProp, } from './managed-objects.js';
|
|
13
|
+
/** MMModemState (`Modem.State`, signed) → the domain `MmState`. */
|
|
14
|
+
function mapMmState(state) {
|
|
15
|
+
switch (state) {
|
|
16
|
+
case -1:
|
|
17
|
+
return 'failed';
|
|
18
|
+
case 1:
|
|
19
|
+
return 'initializing';
|
|
20
|
+
case 2:
|
|
21
|
+
return 'locked';
|
|
22
|
+
case 3:
|
|
23
|
+
return 'disabled';
|
|
24
|
+
case 4:
|
|
25
|
+
return 'disabling';
|
|
26
|
+
case 5:
|
|
27
|
+
return 'enabling';
|
|
28
|
+
case 6:
|
|
29
|
+
return 'enabled';
|
|
30
|
+
case 7:
|
|
31
|
+
return 'searching';
|
|
32
|
+
case 8:
|
|
33
|
+
return 'registered';
|
|
34
|
+
case 9:
|
|
35
|
+
return 'disconnecting';
|
|
36
|
+
case 10:
|
|
37
|
+
return 'connecting';
|
|
38
|
+
case 11:
|
|
39
|
+
return 'connected';
|
|
40
|
+
default:
|
|
41
|
+
return 'unknown';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** MMModemPowerState (`Modem.PowerState`) → the domain `RadioPower`. */
|
|
45
|
+
function mapRadioPower(power) {
|
|
46
|
+
switch (power) {
|
|
47
|
+
case 1:
|
|
48
|
+
return 'off';
|
|
49
|
+
case 2:
|
|
50
|
+
return 'low';
|
|
51
|
+
case 3:
|
|
52
|
+
return 'on';
|
|
53
|
+
default:
|
|
54
|
+
return 'unknown';
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** Locked / failed modems keep the radio off; every other state we cannot see as
|
|
58
|
+
* off (the guards forbid an active MM state with the radio off), so an unknown
|
|
59
|
+
* power reading is treated as `on` when the modem is clearly on the air. */
|
|
60
|
+
function reconcilePower(power, state) {
|
|
61
|
+
const onTheAir = state === 'enabled' ||
|
|
62
|
+
state === 'searching' ||
|
|
63
|
+
state === 'registered' ||
|
|
64
|
+
state === 'connecting' ||
|
|
65
|
+
state === 'connected' ||
|
|
66
|
+
state === 'disconnecting';
|
|
67
|
+
if (onTheAir && (power === 'unknown' || power === 'off')) {
|
|
68
|
+
return 'on';
|
|
69
|
+
}
|
|
70
|
+
return power;
|
|
71
|
+
}
|
|
72
|
+
/** Read a modem's subscription id (ICCID) from its active SIM object, if any. */
|
|
73
|
+
function readSubscriptionId(tree, modemPath) {
|
|
74
|
+
const modem = findInterface(tree, modemPath, MODEM_IFACE);
|
|
75
|
+
const sim = followObjectPath(tree, modem, 'Sim', SIM_IFACE);
|
|
76
|
+
const iccid = stringProp(sim, 'SimIdentifier');
|
|
77
|
+
return iccid !== undefined && iccid.length > 0 ? makeSubscriptionId(iccid) : undefined;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The SIM's own number(s) from `Modem.OwnNumbers`, or `undefined` when the
|
|
81
|
+
* carrier published none. Most SIMs carry no MSISDN at all, so an absent or
|
|
82
|
+
* empty property is the ordinary case and must read as "not reported" rather
|
|
83
|
+
* than as an empty list an operator could mistake for a failed read.
|
|
84
|
+
*/
|
|
85
|
+
function readOwnNumbers(modem) {
|
|
86
|
+
const raw = stringArrayProp(modem, 'OwnNumbers');
|
|
87
|
+
if (raw === undefined) {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
const numbers = raw
|
|
91
|
+
.map((value) => value.trim())
|
|
92
|
+
.filter((value) => value.length > 0)
|
|
93
|
+
.map((value) => makeSubscriberNumber(value));
|
|
94
|
+
return numbers.length > 0 ? numbers : undefined;
|
|
95
|
+
}
|
|
96
|
+
/** Map ONE modem object to its dimensions. The modem must expose `Modem`. */
|
|
97
|
+
export function mapModem(tree, modemPath) {
|
|
98
|
+
const modem = findInterface(tree, modemPath, MODEM_IFACE);
|
|
99
|
+
const modem3gpp = findInterface(tree, modemPath, MODEM3GPP_IFACE);
|
|
100
|
+
const equipment = stringProp(modem, 'EquipmentIdentifier') ?? stringProp(modem3gpp, 'Imei') ?? '';
|
|
101
|
+
const mmState = mapMmState(numberProp(modem, 'State'));
|
|
102
|
+
const radioPower = reconcilePower(mapRadioPower(numberProp(modem, 'PowerState')), mmState);
|
|
103
|
+
const sub = readSubscriptionId(tree, modemPath);
|
|
104
|
+
const ownNumbers = readOwnNumbers(modem);
|
|
105
|
+
return {
|
|
106
|
+
identity: {
|
|
107
|
+
equipmentId: imeiEquipmentId(equipment),
|
|
108
|
+
runtimePath: runtimePath(modemPath),
|
|
109
|
+
...(sub !== undefined ? { subscriptionId: sub } : {}),
|
|
110
|
+
...(ownNumbers !== undefined ? { ownNumbers } : {}),
|
|
111
|
+
},
|
|
112
|
+
presence: 'present',
|
|
113
|
+
sourceHealth: 'live',
|
|
114
|
+
simSlots: [],
|
|
115
|
+
radioPower,
|
|
116
|
+
mmState,
|
|
117
|
+
// Conservative: 3GPP registration + RAT set are A3.3's job (see file header).
|
|
118
|
+
registration: { status: 'unknown', activeRats: new Set() },
|
|
119
|
+
nmActivation: 'unavailable',
|
|
120
|
+
dataInterface: { present: false },
|
|
121
|
+
reconcileStatus: 'pending',
|
|
122
|
+
recoveryState: { stage: 'idle', attempts: 0 },
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/** Every modem path in the tree (objects exposing the `Modem` interface), in order. */
|
|
126
|
+
export function modemPaths(tree) {
|
|
127
|
+
return pathsWithInterface(tree, MODEM_IFACE);
|
|
128
|
+
}
|
|
129
|
+
/** A stable fingerprint of a mapped modem's meaningful dimensions (ignores revision
|
|
130
|
+
* and source health), used to suppress redundant revision bumps on identical reads. */
|
|
131
|
+
export function fingerprint(mapped) {
|
|
132
|
+
return JSON.stringify({
|
|
133
|
+
id: mapped.identity.equipmentId,
|
|
134
|
+
sub: mapped.identity.subscriptionId ?? null,
|
|
135
|
+
own: mapped.identity.ownNumbers ?? null,
|
|
136
|
+
path: mapped.identity.runtimePath,
|
|
137
|
+
presence: mapped.presence,
|
|
138
|
+
radioPower: mapped.radioPower,
|
|
139
|
+
mmState: mapped.mmState,
|
|
140
|
+
registration: {
|
|
141
|
+
status: mapped.registration.status,
|
|
142
|
+
rats: [...mapped.registration.activeRats].sort(),
|
|
143
|
+
},
|
|
144
|
+
nmActivation: mapped.nmActivation,
|
|
145
|
+
dataInterface: mapped.dataInterface,
|
|
146
|
+
reconcileStatus: mapped.reconcileStatus,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { DesiredRadio } from '../domain/index.js';
|
|
2
|
+
import type { BandReadResult, InhibitLease, ModemManagerPort, ModemRef, NetworkScanResult, ObservationList, ObservationListener, Receipt, SimPukUnlockResult, SimUnlockResult, Unsubscribe } from '../ports/index.js';
|
|
3
|
+
import type { DbusTransport } from '../transport/index.js';
|
|
4
|
+
import { type CellReading } from './cell-info.js';
|
|
5
|
+
import { type ModemEnrichment } from './enrichment.js';
|
|
6
|
+
import { ModemActor, type QuiesceHook } from './modem-actor.js';
|
|
7
|
+
import { type SignalCadence } from './signal-setup.js';
|
|
8
|
+
export interface MmDbusBackendOptions {
|
|
9
|
+
readonly transport: DbusTransport;
|
|
10
|
+
readonly destination?: string;
|
|
11
|
+
readonly actor?: ModemActor;
|
|
12
|
+
/** NM quiesce hook for disruptive mode/slot changes (A3.3 default: no-op). */
|
|
13
|
+
readonly quiesce?: QuiesceHook;
|
|
14
|
+
/** Signal.Setup reporting interval in seconds. */
|
|
15
|
+
readonly signalIntervalSeconds?: number;
|
|
16
|
+
readonly scanTimeoutMs?: number;
|
|
17
|
+
readonly now?: () => number;
|
|
18
|
+
}
|
|
19
|
+
/** The real D-Bus ModemManager backend: observation + mutations + Signal.Setup. */
|
|
20
|
+
export declare class MmDbusBackend implements ModemManagerPort {
|
|
21
|
+
#private;
|
|
22
|
+
constructor(options: MmDbusBackendOptions);
|
|
23
|
+
start(): Promise<ObservationList>;
|
|
24
|
+
observe(listener: ObservationListener): Unsubscribe;
|
|
25
|
+
stop(): Promise<void>;
|
|
26
|
+
setRadioModes(modem: ModemRef, preference: DesiredRadio): Promise<Receipt>;
|
|
27
|
+
setModeCombination(modem: ModemRef, allowed: number, preferred: number): Promise<Receipt>;
|
|
28
|
+
setPrimarySimSlot(modem: ModemRef, slotIndex: number): Promise<Receipt>;
|
|
29
|
+
readBands(modem: ModemRef): Promise<BandReadResult>;
|
|
30
|
+
setCurrentBands(modem: ModemRef, bands: readonly string[]): Promise<Receipt>;
|
|
31
|
+
sendPin(modem: ModemRef, pin: string): Promise<SimUnlockResult>;
|
|
32
|
+
sendPuk(modem: ModemRef, puk: string, newPin: string): Promise<SimPukUnlockResult>;
|
|
33
|
+
scanNetworks(modem: ModemRef): Promise<NetworkScanResult>;
|
|
34
|
+
inhibit(uid: string): Promise<InhibitLease>;
|
|
35
|
+
uninhibit(lease: InhibitLease): Promise<void>;
|
|
36
|
+
/** Whether periodic signal reporting is configured for a modem (Signal.Setup). */
|
|
37
|
+
signalCadence(modem: ModemRef): SignalCadence;
|
|
38
|
+
/** Read the normalized visible-cell list for a modem via `Modem.GetCellInfo`. */
|
|
39
|
+
readCellInfo(modem: ModemRef): Promise<readonly CellReading[]>;
|
|
40
|
+
/** Assemble the full read-only enrichment (revision, eSIM, cadence, cell info). */
|
|
41
|
+
readEnrichment(modem: ModemRef): Promise<ModemEnrichment>;
|
|
42
|
+
}
|
|
43
|
+
/** Construct the real D-Bus ModemManager backend over an A2.4 transport. */
|
|
44
|
+
export declare function createMmDbusBackend(options: MmDbusBackendOptions): MmDbusBackend;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// MmDbusBackend — the real D-Bus `ModemManagerPort`, composed from the A3 parts.
|
|
2
|
+
//
|
|
3
|
+
// It fulfils the whole port: the read side delegates to A3.1's epoch-scoped observer;
|
|
4
|
+
// the mutations run through `MmMutations` (each serialized on the shared per-modem
|
|
5
|
+
// `ModemActor`, keyed on A3.2's STABLE key so serialization survives replug); the
|
|
6
|
+
// Signal.Setup lifecycle is driven off the observer's `onEpochRefresh` hook; and the
|
|
7
|
+
// read-only enrichment (firmware revision, eSIM, signal cadence, normalized cell info)
|
|
8
|
+
// is assembled on demand. One transport is shared by all of them.
|
|
9
|
+
//
|
|
10
|
+
// The `onEpochRefresh` hook is the single integration seam: on every current-epoch
|
|
11
|
+
// authoritative snapshot it (1) rebuilds the live path → stable-key map (so a mutation
|
|
12
|
+
// keys the actor by the durable identity, not the transient path) and (2) re-drives
|
|
13
|
+
// Signal.Setup for that epoch — applying to new modems, re-applying to survivors after
|
|
14
|
+
// an owner change, and never firing for a superseded epoch.
|
|
15
|
+
import { epochMillis } from '../domain/index.js';
|
|
16
|
+
import { normalizeCellInfo } from './cell-info.js';
|
|
17
|
+
import { MM_BUS_NAME, MODEM_IFACE } from './constants.js';
|
|
18
|
+
import { buildEnrichment } from './enrichment.js';
|
|
19
|
+
import { modemIdentityFactsFromTree, resolveModemIdentities } from './identity-ladder.js';
|
|
20
|
+
import { fetchManagedObjects, pathsWithInterface } from './managed-objects.js';
|
|
21
|
+
import { MmMutations } from './mm-mutations.js';
|
|
22
|
+
import { ModemActor } from './modem-actor.js';
|
|
23
|
+
import { createMmDbusObserver } from './observer.js';
|
|
24
|
+
import { SignalSetupManager } from './signal-setup.js';
|
|
25
|
+
/** The real D-Bus ModemManager backend: observation + mutations + Signal.Setup. */
|
|
26
|
+
export class MmDbusBackend {
|
|
27
|
+
#transport;
|
|
28
|
+
#destination;
|
|
29
|
+
#now;
|
|
30
|
+
#observer;
|
|
31
|
+
#mutations;
|
|
32
|
+
#signalSetup;
|
|
33
|
+
// Live modem path → durable stable key; rebuilt on every epoch snapshot.
|
|
34
|
+
#stableKeyByPath = new Map();
|
|
35
|
+
constructor(options) {
|
|
36
|
+
this.#transport = options.transport;
|
|
37
|
+
this.#destination = options.destination ?? MM_BUS_NAME;
|
|
38
|
+
this.#now = options.now ?? Date.now;
|
|
39
|
+
const actor = options.actor ?? new ModemActor(options.quiesce);
|
|
40
|
+
this.#signalSetup = new SignalSetupManager({
|
|
41
|
+
transport: this.#transport,
|
|
42
|
+
destination: this.#destination,
|
|
43
|
+
...(options.signalIntervalSeconds !== undefined
|
|
44
|
+
? { intervalSeconds: options.signalIntervalSeconds }
|
|
45
|
+
: {}),
|
|
46
|
+
});
|
|
47
|
+
this.#observer = createMmDbusObserver({
|
|
48
|
+
transport: this.#transport,
|
|
49
|
+
destination: this.#destination,
|
|
50
|
+
onEpochRefresh: (event) => this.#onEpochRefresh(event),
|
|
51
|
+
});
|
|
52
|
+
this.#mutations = new MmMutations({
|
|
53
|
+
transport: this.#transport,
|
|
54
|
+
actor,
|
|
55
|
+
destination: this.#destination,
|
|
56
|
+
resolveStableKey: (modem) => this.#stableKeyByPath.get(modem) ?? modem,
|
|
57
|
+
...(options.scanTimeoutMs !== undefined ? { scanTimeoutMs: options.scanTimeoutMs } : {}),
|
|
58
|
+
now: this.#now,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
// ── observation (delegated to the A3.1 observer) ────────────────────────────────
|
|
62
|
+
start() {
|
|
63
|
+
return this.#observer.start();
|
|
64
|
+
}
|
|
65
|
+
observe(listener) {
|
|
66
|
+
return this.#observer.observe(listener);
|
|
67
|
+
}
|
|
68
|
+
stop() {
|
|
69
|
+
return this.#observer.stop();
|
|
70
|
+
}
|
|
71
|
+
// ── mutations (serialized per modem through the shared actor) ────────────────────
|
|
72
|
+
setRadioModes(modem, preference) {
|
|
73
|
+
return this.#mutations.setRadioModes(modem, preference);
|
|
74
|
+
}
|
|
75
|
+
setModeCombination(modem, allowed, preferred) {
|
|
76
|
+
return this.#mutations.setModeCombination(modem, allowed, preferred);
|
|
77
|
+
}
|
|
78
|
+
setPrimarySimSlot(modem, slotIndex) {
|
|
79
|
+
return this.#mutations.setPrimarySimSlot(modem, slotIndex);
|
|
80
|
+
}
|
|
81
|
+
readBands(modem) {
|
|
82
|
+
return this.#mutations.readBands(modem);
|
|
83
|
+
}
|
|
84
|
+
setCurrentBands(modem, bands) {
|
|
85
|
+
return this.#mutations.setCurrentBands(modem, bands);
|
|
86
|
+
}
|
|
87
|
+
sendPin(modem, pin) {
|
|
88
|
+
return this.#mutations.sendPin(modem, pin);
|
|
89
|
+
}
|
|
90
|
+
sendPuk(modem, puk, newPin) {
|
|
91
|
+
return this.#mutations.sendPuk(modem, puk, newPin);
|
|
92
|
+
}
|
|
93
|
+
scanNetworks(modem) {
|
|
94
|
+
return this.#mutations.scanNetworks(modem);
|
|
95
|
+
}
|
|
96
|
+
inhibit(uid) {
|
|
97
|
+
return this.#mutations.inhibit(uid);
|
|
98
|
+
}
|
|
99
|
+
uninhibit(lease) {
|
|
100
|
+
return this.#mutations.uninhibit(lease);
|
|
101
|
+
}
|
|
102
|
+
// ── enrichment (additive, read-only, never gating) ──────────────────────────────
|
|
103
|
+
/** Whether periodic signal reporting is configured for a modem (Signal.Setup). */
|
|
104
|
+
signalCadence(modem) {
|
|
105
|
+
return this.#signalSetup.cadenceFor(modem);
|
|
106
|
+
}
|
|
107
|
+
/** Read the normalized visible-cell list for a modem via `Modem.GetCellInfo`. */
|
|
108
|
+
async readCellInfo(modem) {
|
|
109
|
+
const provenance = { source: modem, observedAt: epochMillis(this.#now()) };
|
|
110
|
+
try {
|
|
111
|
+
const reply = await this.#transport.callMethod({
|
|
112
|
+
destination: this.#destination,
|
|
113
|
+
path: modem,
|
|
114
|
+
interface: MODEM_IFACE,
|
|
115
|
+
member: 'GetCellInfo',
|
|
116
|
+
});
|
|
117
|
+
const cells = Array.isArray(reply.body[0])
|
|
118
|
+
? reply.body[0]
|
|
119
|
+
: [];
|
|
120
|
+
return normalizeCellInfo(cells, provenance);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return [];
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** Assemble the full read-only enrichment (revision, eSIM, cadence, cell info). */
|
|
127
|
+
async readEnrichment(modem) {
|
|
128
|
+
const [tree, cellInfo] = await Promise.all([
|
|
129
|
+
fetchManagedObjects(this.#transport, this.#destination),
|
|
130
|
+
this.readCellInfo(modem),
|
|
131
|
+
]);
|
|
132
|
+
return buildEnrichment(tree, modem, this.#signalSetup.cadenceFor(modem), cellInfo);
|
|
133
|
+
}
|
|
134
|
+
#onEpochRefresh(event) {
|
|
135
|
+
this.#refreshStableKeys(event);
|
|
136
|
+
this.#signalSetup.applyForEpoch(event.epoch, event.tree);
|
|
137
|
+
}
|
|
138
|
+
#refreshStableKeys(event) {
|
|
139
|
+
const paths = pathsWithInterface(event.tree, MODEM_IFACE);
|
|
140
|
+
const facts = paths.map((path) => modemIdentityFactsFromTree(event.tree, path));
|
|
141
|
+
const resolved = resolveModemIdentities(facts);
|
|
142
|
+
this.#stableKeyByPath.clear();
|
|
143
|
+
paths.forEach((path, index) => {
|
|
144
|
+
const entry = resolved[index];
|
|
145
|
+
if (entry !== undefined) {
|
|
146
|
+
this.#stableKeyByPath.set(path, entry.stableKey);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/** Construct the real D-Bus ModemManager backend over an A2.4 transport. */
|
|
152
|
+
export function createMmDbusBackend(options) {
|
|
153
|
+
return new MmDbusBackend(options);
|
|
154
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { FixRead, GnssSource, LocationStatusResult, LocationToggleResult, ModemLocationPort, ModemRef } from '../ports/index.js';
|
|
2
|
+
import type { DbusTransport } from '../transport/index.js';
|
|
3
|
+
import type { ModemActor } from './modem-actor.js';
|
|
4
|
+
/** Decode an MMModemLocationSource bitmask into source names. */
|
|
5
|
+
export declare function decodeLocationSources(mask: number): ReadonlySet<string>;
|
|
6
|
+
export declare function encodeLocationSources(sources: Iterable<string>): number;
|
|
7
|
+
export interface MmLocationDeps {
|
|
8
|
+
readonly transport: DbusTransport;
|
|
9
|
+
readonly actor: ModemActor;
|
|
10
|
+
readonly destination?: string;
|
|
11
|
+
readonly resolveStableKey: (modem: ModemRef) => string;
|
|
12
|
+
readonly now?: () => number;
|
|
13
|
+
}
|
|
14
|
+
export declare class MmLocation implements ModemLocationPort {
|
|
15
|
+
#private;
|
|
16
|
+
constructor(deps: MmLocationDeps);
|
|
17
|
+
getLocationStatus(modem: ModemRef): Promise<LocationStatusResult>;
|
|
18
|
+
enableGnss(modem: ModemRef, sources: readonly GnssSource[]): Promise<LocationToggleResult>;
|
|
19
|
+
disableGnss(modem: ModemRef): Promise<LocationToggleResult>;
|
|
20
|
+
readFix(modem: ModemRef): Promise<FixRead>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
// The `Modem.Location` adapter — GNSS status, enable/disable, and the current fix.
|
|
2
|
+
//
|
|
3
|
+
// Three decisions here are load-bearing and easy to undo by accident:
|
|
4
|
+
//
|
|
5
|
+
// 1. `Setup`'s `signal_location` argument is ALWAYS false. Passing true makes MM
|
|
6
|
+
// broadcast the `Location` property over `PropertiesChanged`, which would put
|
|
7
|
+
// the operator's coordinates on the system bus for every listener — including
|
|
8
|
+
// this package's own observer, whose snapshots are logged. The fix is fetched
|
|
9
|
+
// by an explicit `GetLocation` call instead, so a coordinate only ever exists
|
|
10
|
+
// where someone asked for it.
|
|
11
|
+
// 2. GNSS runs through `actor.run`, NOT `actor.runQuiesced`. Quiescing exists to
|
|
12
|
+
// stop NetworkManager racing a disruptive change, and it costs a bearer
|
|
13
|
+
// deactivation — on a bonded device, dropping a link to switch a GPS receiver
|
|
14
|
+
// on would be an absurd trade. `Modem.Location.Setup` touches no bearer, so
|
|
15
|
+
// serialization per modem is all that is needed.
|
|
16
|
+
// 3. Disable CLEARS only the GNSS bits. `3gpp-lac-ci` is the cell-info module's
|
|
17
|
+
// source; blanking the whole mask would silently switch off a neighbouring
|
|
18
|
+
// feature the operator never touched.
|
|
19
|
+
import { epochMillis } from '../domain/index.js';
|
|
20
|
+
import { parseNmeaFix } from '../location/nmea.js';
|
|
21
|
+
import { hasGnssSource } from '../ports/index.js';
|
|
22
|
+
import { MM_BUS_NAME, MODEM_LOCATION_IFACE } from './constants.js';
|
|
23
|
+
import { fetchManagedObjects, findInterface, propValue, } from './managed-objects.js';
|
|
24
|
+
/** MMModemLocationSource bits, by decoded source name. */
|
|
25
|
+
const SOURCE_BIT = {
|
|
26
|
+
'3gpp-lac-ci': 1 << 0,
|
|
27
|
+
'gps-raw': 1 << 1,
|
|
28
|
+
'gps-nmea': 1 << 2,
|
|
29
|
+
'cdma-bs': 1 << 3,
|
|
30
|
+
'gps-unmanaged': 1 << 4,
|
|
31
|
+
'agps-msa': 1 << 5,
|
|
32
|
+
'agps-msb': 1 << 6,
|
|
33
|
+
};
|
|
34
|
+
const GPS_RAW_BIT = SOURCE_BIT['gps-raw'];
|
|
35
|
+
const GPS_NMEA_BIT = SOURCE_BIT['gps-nmea'];
|
|
36
|
+
const GNSS_MASK = GPS_RAW_BIT |
|
|
37
|
+
GPS_NMEA_BIT |
|
|
38
|
+
SOURCE_BIT['gps-unmanaged'] |
|
|
39
|
+
SOURCE_BIT['agps-msa'] |
|
|
40
|
+
SOURCE_BIT['agps-msb'];
|
|
41
|
+
/** Decode an MMModemLocationSource bitmask into source names. */
|
|
42
|
+
export function decodeLocationSources(mask) {
|
|
43
|
+
const sources = new Set();
|
|
44
|
+
for (const [name, bit] of Object.entries(SOURCE_BIT)) {
|
|
45
|
+
if ((mask & bit) !== 0) {
|
|
46
|
+
sources.add(name);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return sources;
|
|
50
|
+
}
|
|
51
|
+
export function encodeLocationSources(sources) {
|
|
52
|
+
let mask = 0;
|
|
53
|
+
for (const source of sources) {
|
|
54
|
+
mask |= SOURCE_BIT[source] ?? 0;
|
|
55
|
+
}
|
|
56
|
+
return mask;
|
|
57
|
+
}
|
|
58
|
+
export class MmLocation {
|
|
59
|
+
#transport;
|
|
60
|
+
#actor;
|
|
61
|
+
#destination;
|
|
62
|
+
#resolveStableKey;
|
|
63
|
+
#now;
|
|
64
|
+
constructor(deps) {
|
|
65
|
+
this.#transport = deps.transport;
|
|
66
|
+
this.#actor = deps.actor;
|
|
67
|
+
this.#destination = deps.destination ?? MM_BUS_NAME;
|
|
68
|
+
this.#resolveStableKey = deps.resolveStableKey;
|
|
69
|
+
this.#now = deps.now ?? Date.now;
|
|
70
|
+
}
|
|
71
|
+
async getLocationStatus(modem) {
|
|
72
|
+
const props = await this.#readLocationProps(modem);
|
|
73
|
+
if (props === undefined) {
|
|
74
|
+
return { ok: false, reason: 'the modem does not expose a Location interface' };
|
|
75
|
+
}
|
|
76
|
+
return { ok: true, status: statusOf(props) };
|
|
77
|
+
}
|
|
78
|
+
enableGnss(modem, sources) {
|
|
79
|
+
return this.#actor.run(this.#resolveStableKey(modem), async () => {
|
|
80
|
+
const props = await this.#readLocationProps(modem);
|
|
81
|
+
if (props === undefined) {
|
|
82
|
+
return refused('the modem does not expose a Location interface');
|
|
83
|
+
}
|
|
84
|
+
const status = statusOf(props);
|
|
85
|
+
if (!status.gnssCapable) {
|
|
86
|
+
return refused('the modem advertises no GNSS source', status.enabledSources);
|
|
87
|
+
}
|
|
88
|
+
const requested = encodeLocationSources(sources) & maskOfNames(status.capabilities);
|
|
89
|
+
if (requested === 0) {
|
|
90
|
+
return refused('none of the requested GNSS sources is advertised by this modem', status.enabledSources);
|
|
91
|
+
}
|
|
92
|
+
const target = maskOfNames(status.enabledSources) | requested;
|
|
93
|
+
return this.#setup(modem, target, 'GNSS enabled');
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
disableGnss(modem) {
|
|
97
|
+
return this.#actor.run(this.#resolveStableKey(modem), async () => {
|
|
98
|
+
const props = await this.#readLocationProps(modem);
|
|
99
|
+
if (props === undefined) {
|
|
100
|
+
return refused('the modem does not expose a Location interface');
|
|
101
|
+
}
|
|
102
|
+
const status = statusOf(props);
|
|
103
|
+
const target = maskOfNames(status.enabledSources) & ~GNSS_MASK;
|
|
104
|
+
return this.#setup(modem, target, 'GNSS disabled');
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
async readFix(modem) {
|
|
108
|
+
const props = await this.#readLocationProps(modem);
|
|
109
|
+
if (props === undefined) {
|
|
110
|
+
return { outcome: 'unsupported', reason: 'the modem does not expose a Location interface' };
|
|
111
|
+
}
|
|
112
|
+
const status = statusOf(props);
|
|
113
|
+
if (!status.gnssCapable) {
|
|
114
|
+
return { outcome: 'unsupported', reason: 'the modem advertises no GNSS source' };
|
|
115
|
+
}
|
|
116
|
+
if (!status.gnssEnabled) {
|
|
117
|
+
return { outcome: 'disabled', reason: 'GNSS is switched off on this modem' };
|
|
118
|
+
}
|
|
119
|
+
let reply;
|
|
120
|
+
try {
|
|
121
|
+
reply = await this.#transport.callMethod({
|
|
122
|
+
destination: this.#destination,
|
|
123
|
+
path: modem,
|
|
124
|
+
interface: MODEM_LOCATION_IFACE,
|
|
125
|
+
member: 'GetLocation',
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
return { outcome: 'error', reason: `GetLocation failed: ${describe(error)}` };
|
|
130
|
+
}
|
|
131
|
+
const fix = decodeFix(reply.body[0], epochMillis(this.#now()));
|
|
132
|
+
return fix === undefined
|
|
133
|
+
? { outcome: 'no-fix', reason: 'the receiver has not acquired a position' }
|
|
134
|
+
: { outcome: 'fix', fix };
|
|
135
|
+
}
|
|
136
|
+
async #setup(modem, mask, appliedReason) {
|
|
137
|
+
try {
|
|
138
|
+
await this.#transport.callMethod({
|
|
139
|
+
destination: this.#destination,
|
|
140
|
+
path: modem,
|
|
141
|
+
interface: MODEM_LOCATION_IFACE,
|
|
142
|
+
member: 'Setup',
|
|
143
|
+
signature: 'ub',
|
|
144
|
+
args: [mask, false],
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
return {
|
|
149
|
+
outcome: 'failed',
|
|
150
|
+
reason: `Location.Setup failed: ${describe(error)}`,
|
|
151
|
+
enabledSources: new Set(),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
outcome: 'applied',
|
|
156
|
+
reason: appliedReason,
|
|
157
|
+
enabledSources: decodeLocationSources(mask),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
async #readLocationProps(modem) {
|
|
161
|
+
try {
|
|
162
|
+
const tree = await fetchManagedObjects(this.#transport, this.#destination);
|
|
163
|
+
return findInterface(tree, modem, MODEM_LOCATION_IFACE);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function maskOfNames(names) {
|
|
171
|
+
return encodeLocationSources(names);
|
|
172
|
+
}
|
|
173
|
+
function refused(reason, enabled = new Set()) {
|
|
174
|
+
return { outcome: 'unsupported', reason, enabledSources: enabled };
|
|
175
|
+
}
|
|
176
|
+
function statusOf(props) {
|
|
177
|
+
const capabilities = decodeLocationSources(maskProp(props, 'Capabilities'));
|
|
178
|
+
const enabledSources = decodeLocationSources(maskProp(props, 'Enabled'));
|
|
179
|
+
return {
|
|
180
|
+
capabilities,
|
|
181
|
+
enabledSources,
|
|
182
|
+
gnssCapable: hasGnssSource(capabilities),
|
|
183
|
+
gnssEnabled: hasGnssSource(enabledSources),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
function maskProp(props, name) {
|
|
187
|
+
const value = propValue(props, name);
|
|
188
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
|
189
|
+
}
|
|
190
|
+
function entryValue(payload, bit) {
|
|
191
|
+
if (!Array.isArray(payload)) {
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
for (const entry of payload) {
|
|
195
|
+
if (Number(entry[0]) === bit) {
|
|
196
|
+
return entry[1]?.value;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
201
|
+
function numberField(props, name) {
|
|
202
|
+
const value = propValue(props, name);
|
|
203
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Prefer MM's pre-decoded `gps-raw` dict; fall back to parsing the `gps-nmea`
|
|
207
|
+
* sentences. A raw entry that is present but carries no usable coordinate pair is
|
|
208
|
+
* NOT treated as a fix — MM populates the key as soon as the source is on.
|
|
209
|
+
*/
|
|
210
|
+
function decodeFix(payload, observedAt) {
|
|
211
|
+
const raw = entryValue(payload, GPS_RAW_BIT);
|
|
212
|
+
if (Array.isArray(raw)) {
|
|
213
|
+
const props = raw;
|
|
214
|
+
const latitude = numberField(props, 'latitude');
|
|
215
|
+
const longitude = numberField(props, 'longitude');
|
|
216
|
+
if (latitude !== undefined && longitude !== undefined) {
|
|
217
|
+
const altitude = numberField(props, 'altitude');
|
|
218
|
+
const utcTime = propValue(props, 'utc-time');
|
|
219
|
+
return {
|
|
220
|
+
latitude,
|
|
221
|
+
longitude,
|
|
222
|
+
...(altitude === undefined ? {} : { altitude }),
|
|
223
|
+
...(typeof utcTime === 'string' ? { utcTime } : {}),
|
|
224
|
+
observedAt,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const nmea = entryValue(payload, GPS_NMEA_BIT);
|
|
229
|
+
if (typeof nmea !== 'string') {
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
const parsed = parseNmeaFix(nmea);
|
|
233
|
+
return parsed === undefined ? undefined : { ...parsed, observedAt };
|
|
234
|
+
}
|
|
235
|
+
function describe(error) {
|
|
236
|
+
return error instanceof Error ? error.message : String(error);
|
|
237
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { DesiredRadio } from '../domain/index.js';
|
|
2
|
+
import type { BandReadResult, InhibitLease, ModemRef, NetworkScanResult, Receipt, SimPukUnlockResult, SimUnlockResult } from '../ports/index.js';
|
|
3
|
+
import type { DbusTransport } from '../transport/index.js';
|
|
4
|
+
import type { ModemActor } from './modem-actor.js';
|
|
5
|
+
export interface MmMutationsDeps {
|
|
6
|
+
readonly transport: DbusTransport;
|
|
7
|
+
readonly actor: ModemActor;
|
|
8
|
+
readonly destination?: string;
|
|
9
|
+
/** Map a live modem path to its stable actor key (survives replug). */
|
|
10
|
+
readonly resolveStableKey: (modem: ModemRef) => string;
|
|
11
|
+
readonly scanTimeoutMs?: number;
|
|
12
|
+
readonly now?: () => number;
|
|
13
|
+
}
|
|
14
|
+
/** The disruptive + SIM mutations of `ModemManagerPort`, serialized per modem. */
|
|
15
|
+
export declare class MmMutations {
|
|
16
|
+
#private;
|
|
17
|
+
constructor(deps: MmMutationsDeps);
|
|
18
|
+
setRadioModes(modem: ModemRef, preference: DesiredRadio): Promise<Receipt>;
|
|
19
|
+
/**
|
|
20
|
+
* `SetCurrentModes` over the RAW `(uu)` masks the modem itself advertised.
|
|
21
|
+
*
|
|
22
|
+
* `setRadioModes` above speaks the reconciler's vocabulary — an ordered RAT
|
|
23
|
+
* preference — and structurally cannot express `MM_MODEM_MODE_NONE`: its preferred
|
|
24
|
+
* mask is derived from `preferenceOrdered[0]`, so "allow this set and prefer
|
|
25
|
+
* nothing within it" has no spelling. That is the combination the bench FM350-GL
|
|
26
|
+
* actually advertises, so a caller selecting an advertised combination verbatim
|
|
27
|
+
* needs this entry point. It quiesces for the same reason `setRadioModes` does: a
|
|
28
|
+
* mode change re-registers the radio and drops the bearer underneath NM.
|
|
29
|
+
*
|
|
30
|
+
* An `allowed` mask of 0 is refused — that is not a selection, it is a radio with
|
|
31
|
+
* nothing switched on — while a `preferred` mask of 0 is passed through untouched.
|
|
32
|
+
*/
|
|
33
|
+
setModeCombination(modem: ModemRef, allowed: number, preferred: number): Promise<Receipt>;
|
|
34
|
+
readBands(modem: ModemRef): Promise<BandReadResult>;
|
|
35
|
+
setCurrentBands(modem: ModemRef, bands: readonly string[]): Promise<Receipt>;
|
|
36
|
+
setPrimarySimSlot(modem: ModemRef, slotIndex: number): Promise<Receipt>;
|
|
37
|
+
sendPin(modem: ModemRef, pin: string): Promise<SimUnlockResult>;
|
|
38
|
+
sendPuk(modem: ModemRef, puk: string, newPin: string): Promise<SimPukUnlockResult>;
|
|
39
|
+
scanNetworks(modem: ModemRef): Promise<NetworkScanResult>;
|
|
40
|
+
inhibit(uid: string): Promise<InhibitLease>;
|
|
41
|
+
uninhibit(lease: InhibitLease): Promise<void>;
|
|
42
|
+
}
|