@tempivo/sensor-beacon 1.1.9 → 1.1.10

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/CHANGELOG.md CHANGED
@@ -6,11 +6,20 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.1.10] - 2026-10-14
10
+
11
+ ### Added
12
+
13
+ - `getOperator()`: read preferred PLMN from device config → `'AUTO'`, country ISO (`'SE'`, …), or `null` if custom PLMN is outside the SDK map.
14
+ - `getCellularNetworkStatus().operator`: same code as `getOperator()` (config read in parallel with modem status). Use this to skip `setOperator` when already correct.
15
+
9
16
  ## [1.1.9] - 2026-10-14
10
17
 
11
18
  ### Changed
12
19
 
13
20
  - npm package metadata: drop private GitHub `repository` so npmjs.com no longer links Changelog / bugs to an inaccessible repo. Changelog remains in the published package (`CHANGELOG.md`).
21
+ - `setOperator` / `resolveSetOperator`: unknown country ISO → `AUTO` (no silent Sweden `24001` fallback). Invalid shape (not two letters) still throws.
22
+ - `getCellularNetworkStatus` return: one flat object (`label`, `statusLabel`, `operatorName`, `operatorPlmn`, IP/RSSI/…). No nested `status`, no raw `json` string, no `summary` / `tone` / `legacySession`. Docs: scan `telemetry.cellularStatus` (`cell_ok`, …) for coarse modem state without GATT.
14
23
 
15
24
  ## [1.1.8] - 2026-10-14
16
25
 
package/README.md CHANGED
@@ -113,19 +113,27 @@ The Expo config plugin sets **minSdk 26**, Android BLE permissions, and **`NSBlu
113
113
  | `getConfigurationErrors` | Read decoded `configurationErrors` from ProtoConfig (BLE get). Empty list when none. |
114
114
  | `triggerTransmission` | Ask firmware to uplink now; returns `{ ok }`. Cloud pending syncs after that uplink (or schedule/button), not from the BLE write alone. |
115
115
  | `clearMeasurementQueue` / `deleteMeasurements` | Clear on-device measurement / uplink queue (memory reset). Manual recovery only; not used by `setConfigurationJson`. |
116
- | `getCellularNetworkStatus` / `getCellularNetworkStatusJson` / `setOperator` | GATT network status with short labels (`label` includes operator when known, plus `statusLabel` / `operatorName` / `summary` / `tone`; `locale`: `en` \| `sv`), raw JSON, PLMN `AUTO` or country ISO (`SE`, …). |
117
- | `cellularNetworkStatusViewFromJson` (+ related) | Pure label helpers from stored GATT JSON (no radio). |
116
+ | `getCellularNetworkStatus` | GATT read (after `connect`). UI fields + parsed modem fields (`locale`: `en` \| `sv`). Includes `operator` (`'AUTO'` / ISO) from device preferred PLMN. |
117
+ | `getOperator` | Preferred PLMN only (`'AUTO'` / ISO / `null`). Same source as `setOperator`. |
118
+ | `setOperator` | Set preferred PLMN: `'AUTO'` or country ISO (`'SE'`, `'NO'`, …). Unknown ISO → `AUTO`. |
118
119
  | `getCalibration` | BLE read of lab calibration. Returns `laboratoryCalibrationDate` (`YYYY-MM-DD`) and unix `laboratoryCalibrationTimestamp` (start of that day). Device has no wall-clock time. Prefer Cellular API in production. |
119
120
 
120
- ```ts
121
- const { label, statusLabel, operatorName, summary, tone } = await getCellularNetworkStatus({
122
- locale: 'en',
123
- });
124
- // label: "Telia Sweden, Server OK" (operator from GATT rplmn when known)
125
- // statusLabel: "No server" | "No internet" | "Server OK" | …
126
- // operatorName: "Telia Sweden" | null
121
+ **Cellular without connect:** scan telemetry already has a coarse modem hint: `telemetry.cellularStatus` = `ble_only` \| `cell_ok` \| `no_server` \| `net_issue` (frame `0x03` bits 6–7). Use that for “looks OK / no server” while scanning. Use `getCellularNetworkStatus` only when you need IP, RSSI, SIM, operator name, etc.
127
122
 
128
- await setOperator('AUTO'); // or 'SE', 'NO', …
123
+ ```ts
124
+ // From scan (no GATT):
125
+ device.telemetry?.cellularStatus; // e.g. "cell_ok"
126
+
127
+ // After connect:
128
+ const s = await getCellularNetworkStatus({ locale: 'en' });
129
+ // s.operator → "AUTO" | "SE" | … (preferred; same as setOperator)
130
+ // s.operatorName → "Telia Sweden" (registered network label)
131
+ // s.operatorPlmn → modem rplmn (telemetry only; do not compare this to setOperator)
132
+
133
+ // Set operator based on country, or AUTO if cross-country usage.
134
+ // Recommended to set operator country if only one country is used for the session.
135
+ if (s.operator !== 'SE') await setOperator('SE');
136
+ // or: const op = await getOperator();
129
137
  ```
130
138
 
131
139
  **Cloud queue:** BLE updates the sensor only. After uplink, Tempivo drops from Cellular `pendingConfig` only fields the device already matches; other queued fields can still downlink. Cancel the whole queue with `DELETE /api/v1/devices/{serial}/config` (see Tempivo Cellular API docs).
@@ -152,6 +152,24 @@ final class SensorBeaconNative {
152
152
  }
153
153
  }
154
154
 
155
+ Map<String, Object> getOperator() {
156
+ try {
157
+ kotlin.Pair<Boolean, Integer> pair = requireSessionConnected().getOperatorBlocking();
158
+ Map<String, Object> payload = new HashMap<>();
159
+ boolean automatic = Boolean.TRUE.equals(pair.getFirst());
160
+ payload.put("automatic", automatic);
161
+ Integer plmn = pair.getSecond();
162
+ if (!automatic && plmn != null) {
163
+ payload.put("plmn", plmn);
164
+ } else {
165
+ payload.put("plmn", null);
166
+ }
167
+ return payload;
168
+ } catch (Throwable error) {
169
+ throw wrap(error);
170
+ }
171
+ }
172
+
155
173
  Map<String, Object> getCalibration() {
156
174
  try {
157
175
  return calibrationMap(requireSessionConnected().getCalibrationBlocking());
@@ -116,6 +116,14 @@ class TempivoSensorBeaconModule : Module() {
116
116
  }
117
117
  }
118
118
 
119
+ AsyncFunction("getOperator") {
120
+ try {
121
+ native.getOperator()
122
+ } catch (error: Throwable) {
123
+ throw error.toSensorException()
124
+ }
125
+ }
126
+
119
127
  AsyncFunction("getCalibration") {
120
128
  try {
121
129
  native.calibration
@@ -183,6 +183,18 @@ internal object SensorBleRuntime {
183
183
  session.connection.commands.setConfiguration(next) { }
184
184
  }
185
185
 
186
+ /**
187
+ * Read preferred PLMN from live SensorConfiguration (same source [setOperator] writes).
188
+ * @return automatic=true for AUTO; otherwise preferred operator code in [plmn].
189
+ */
190
+ suspend fun getOperator(session: Session): Pair<Boolean, Int?> {
191
+ val cfg = modernGetConfiguration(session.connection)
192
+ return when (val p = cfg.plmn) {
193
+ null, is Plmn.Automatic -> true to null
194
+ is Plmn.Custom -> false to p.operatorCode
195
+ }
196
+ }
197
+
186
198
  /** Truncate rule slots SDK 2.3 rejects on setConfiguration (>12 rules / >6 calendars). */
187
199
  private fun prepareForSetConfiguration(cfg: SensorConfiguration): SensorConfiguration {
188
200
  var out = cfg
@@ -195,6 +195,14 @@ class TempivoSensorSession(
195
195
  }
196
196
  }
197
197
 
198
+ /** Preferred PLMN from device config. first=automatic, second=custom code or null. */
199
+ suspend fun getOperator(): Pair<Boolean, Int?> {
200
+ return runGatt {
201
+ ensureSdkConnected()
202
+ SensorBleRuntime.getOperator(requireSession())
203
+ }
204
+ }
205
+
198
206
  suspend fun getCalibration(): TempivoSensorCalibration {
199
207
  return runGatt {
200
208
  ensureSdkConnected()
@@ -223,6 +231,8 @@ class TempivoSensorSession(
223
231
 
224
232
  fun setOperatorBlocking(automatic: Boolean, plmn: Int?) = runBlocking { setOperator(automatic, plmn) }
225
233
 
234
+ fun getOperatorBlocking(): Pair<Boolean, Int?> = runBlocking { getOperator() }
235
+
226
236
  fun getCalibrationBlocking(): TempivoSensorCalibration = runBlocking { getCalibration() }
227
237
 
228
238
  private fun requireSession(): SensorBleRuntime.Session =
@@ -14,34 +14,40 @@ export type TempivoCellularNetworkStatus = {
14
14
  simCardStatus: string | null;
15
15
  /** Registered / serving PLMN from GATT (`rplmn`). */
16
16
  registeredPlmn: number | null;
17
- legacySession: boolean;
18
17
  };
19
18
  export type TempivoCellularStatusLocale = 'en' | 'sv';
19
+ /** Optional UI severity helper (not returned by {@link getCellularNetworkStatus}). */
20
20
  export type TempivoCellularStatusTone = 'ok' | 'warn' | 'bad' | 'muted';
21
+ /** Partner-facing GATT cellular status (one object, no nested raw string). */
21
22
  export type TempivoCellularNetworkStatusView = {
22
- /** Raw JSON from native GATT. */
23
- json: string;
24
- status: TempivoCellularNetworkStatus | null;
25
- /** Registered PLMN from GATT (`rplmn`), when present. */
26
- operatorPlmn: number | null;
27
- /** Operator name (e.g. Tele2 Sweden), or `PLMN {code}` when unknown. */
28
- operatorName: string | null;
29
- /**
30
- * Short chip label. Status only, or `Operator, status` when PLMN is known
31
- * (e.g. Tele2 Sweden, No server).
32
- */
23
+ /** Chip / row: `Operator, status` when PLMN known, else status only. */
33
24
  label: string;
34
- /** Status without operator (No internet, No server, Server OK). */
25
+ /** Human status (No SIM, No internet, No server, Server OK, …). */
35
26
  statusLabel: string;
36
- /** One-line summary for toasts / logs (includes operator when known). */
37
- summary: string;
38
- tone: TempivoCellularStatusTone;
27
+ /**
28
+ * Preferred operator for {@link setOperator}: `'AUTO'`, country ISO (`'SE'`, …), or `null`
29
+ * when the device has a custom PLMN outside the SDK country map. Filled by
30
+ * {@link getCellularNetworkStatus} / {@link getOperator} (config read), not from modem `rplmn`.
31
+ */
32
+ operator: string | null;
33
+ /** Operator name from registered PLMN (e.g. Telia Sweden), or null. */
34
+ operatorName: string | null;
35
+ /** Registered PLMN from GATT (`rplmn`), or null. */
36
+ operatorPlmn: number | null;
37
+ ipAddress: string | null;
38
+ registrationStatus: string | null;
39
+ serverCommunicationStatus: string | null;
40
+ communicationStatus: string | null;
41
+ signalStrengthDbm: number | null;
42
+ ecl: 0 | 1 | 2 | null;
43
+ rsrpDbm: number | null;
44
+ simCardStatus: string | null;
39
45
  };
40
46
  export declare function isValidCellularIpAddress(ip: string | null | undefined): boolean;
41
47
  export declare function normalizeCellularSignalStrengthDbm(raw: number | null | undefined): number | null;
42
- export declare function parseCellularNetworkStatusObject(obj: Record<string, unknown>, legacySession?: boolean): TempivoCellularNetworkStatus;
48
+ export declare function parseCellularNetworkStatusObject(obj: Record<string, unknown>): TempivoCellularNetworkStatus;
43
49
  /** Parse Gson/SDK JSON from {@link getCellularNetworkStatusJson}. */
44
- export declare function parseCellularNetworkStatusJson(json: string, legacySession?: boolean): TempivoCellularNetworkStatus | null;
50
+ export declare function parseCellularNetworkStatusJson(json: string): TempivoCellularNetworkStatus | null;
45
51
  export declare function isCellularRegistrationOk(status: TempivoCellularNetworkStatus): boolean;
46
52
  export declare function isSimCardOk(status: TempivoCellularNetworkStatus): boolean;
47
53
  export declare function isCellularServerCommunicationOk(status: TempivoCellularNetworkStatus): boolean;
@@ -64,6 +70,5 @@ export declare function resolveCellularStatusTone(status: TempivoCellularNetwork
64
70
  /** Build a partner-ready view from raw GATT JSON (no radio). */
65
71
  export declare function cellularNetworkStatusViewFromJson(json: string, opts?: {
66
72
  locale?: TempivoCellularStatusLocale;
67
- afterServerTrigger?: boolean;
68
- legacySession?: boolean;
73
+ operator?: string | null;
69
74
  }): TempivoCellularNetworkStatusView;
@@ -123,7 +123,7 @@ function readPlmnField(obj, key) {
123
123
  return Number.parseInt(v.trim(), 10);
124
124
  return null;
125
125
  }
126
- export function parseCellularNetworkStatusObject(obj, legacySession = false) {
126
+ export function parseCellularNetworkStatusObject(obj) {
127
127
  const signalRaw = readNumberField(obj, 'signalStrength') ?? readNumberField(obj, 'rssi');
128
128
  const rsrpRaw = readNumberField(obj, 'rsrp') ?? readNumberField(obj, 'referenceSignalReceivedPower');
129
129
  return {
@@ -136,17 +136,16 @@ export function parseCellularNetworkStatusObject(obj, legacySession = false) {
136
136
  rsrpDbm: rsrpRaw == null || !Number.isFinite(rsrpRaw) || rsrpRaw === 0 ? null : Math.round(rsrpRaw),
137
137
  simCardStatus: readStringField(obj, 'simCardStatus'),
138
138
  registeredPlmn: readPlmnField(obj, 'rplmn'),
139
- legacySession,
140
139
  };
141
140
  }
142
141
  /** Parse Gson/SDK JSON from {@link getCellularNetworkStatusJson}. */
143
- export function parseCellularNetworkStatusJson(json, legacySession = false) {
142
+ export function parseCellularNetworkStatusJson(json) {
144
143
  const trimmed = json.trim();
145
144
  if (!trimmed)
146
145
  return null;
147
146
  try {
148
147
  const obj = JSON.parse(trimmed);
149
- return parseCellularNetworkStatusObject(obj, legacySession);
148
+ return parseCellularNetworkStatusObject(obj);
150
149
  }
151
150
  catch {
152
151
  return null;
@@ -161,11 +160,11 @@ export function isSimCardOk(status) {
161
160
  return OK_SIM_CARD_STATUSES.has(status.simCardStatus.trim().toUpperCase());
162
161
  }
163
162
  export function isCellularServerCommunicationOk(status) {
164
- if (status.legacySession) {
165
- return status.communicationStatus != null && OK_COMM_STATUSES.has(status.communicationStatus);
166
- }
167
- return (status.serverCommunicationStatus != null &&
168
- OK_COMM_STATUSES.has(status.serverCommunicationStatus));
163
+ const modern = status.serverCommunicationStatus;
164
+ if (modern != null && OK_COMM_STATUSES.has(modern))
165
+ return true;
166
+ const legacy = status.communicationStatus;
167
+ return legacy != null && OK_COMM_STATUSES.has(legacy);
169
168
  }
170
169
  export function isCellularConnectionFullyOk(status) {
171
170
  return (isValidCellularIpAddress(status.ipAddress) &&
@@ -174,7 +173,7 @@ export function isCellularConnectionFullyOk(status) {
174
173
  isCellularServerCommunicationOk(status));
175
174
  }
176
175
  function rawServerStatus(status) {
177
- const raw = status.legacySession ? status.communicationStatus : status.serverCommunicationStatus;
176
+ const raw = status.serverCommunicationStatus ?? status.communicationStatus;
178
177
  return raw?.trim() ? raw.trim() : null;
179
178
  }
180
179
  function normalizeCommKey(raw) {
@@ -258,35 +257,45 @@ export function resolveCellularStatusTone(status) {
258
257
  return 'bad';
259
258
  return 'warn';
260
259
  }
260
+ function emptyView(label, operator = null) {
261
+ return {
262
+ label,
263
+ statusLabel: label,
264
+ operator,
265
+ operatorName: null,
266
+ operatorPlmn: null,
267
+ ipAddress: null,
268
+ registrationStatus: null,
269
+ serverCommunicationStatus: null,
270
+ communicationStatus: null,
271
+ signalStrengthDbm: null,
272
+ ecl: null,
273
+ rsrpDbm: null,
274
+ simCardStatus: null,
275
+ };
276
+ }
261
277
  /** Build a partner-ready view from raw GATT JSON (no radio). */
262
278
  export function cellularNetworkStatusViewFromJson(json, opts) {
263
279
  const locale = opts?.locale ?? 'en';
280
+ const operator = opts?.operator ?? null;
264
281
  const L = labelsFor(locale);
265
- const status = parseCellularNetworkStatusJson(json, opts?.legacySession === true);
266
- if (!status) {
267
- return {
268
- json,
269
- status: null,
270
- operatorPlmn: null,
271
- operatorName: null,
272
- label: L.unavailable,
273
- statusLabel: L.unavailable,
274
- summary: L.unavailable,
275
- tone: 'muted',
276
- };
277
- }
282
+ const status = parseCellularNetworkStatusJson(json);
283
+ if (!status)
284
+ return emptyView(L.unavailable, operator);
278
285
  const statusLabel = formatCellularNetworkStatusLabel(status, locale);
279
286
  return {
280
- json,
281
- status,
282
- operatorPlmn: status.registeredPlmn,
283
- operatorName: getOperatorNameFromPlmn(status.registeredPlmn),
284
287
  label: formatCellularNetworkStatusLabelWithOperator(status, locale),
285
288
  statusLabel,
286
- summary: formatCellularNetworkStatusSummary(status, {
287
- locale,
288
- afterServerTrigger: opts?.afterServerTrigger,
289
- }),
290
- tone: resolveCellularStatusTone(status),
289
+ operator,
290
+ operatorName: getOperatorNameFromPlmn(status.registeredPlmn),
291
+ operatorPlmn: status.registeredPlmn,
292
+ ipAddress: status.ipAddress,
293
+ registrationStatus: status.registrationStatus,
294
+ serverCommunicationStatus: status.serverCommunicationStatus,
295
+ communicationStatus: status.communicationStatus,
296
+ signalStrengthDbm: status.signalStrengthDbm,
297
+ ecl: status.ecl,
298
+ rsrpDbm: status.rsrpDbm,
299
+ simCardStatus: status.simCardStatus,
291
300
  };
292
301
  }
package/dist/index.d.ts CHANGED
@@ -16,10 +16,10 @@ export type { ProbeCapabilitySource } from './channel-capability.js';
16
16
  export { configurationErrorsFromProtoConfig, decodeConfigurationError, decodeConfigurationErrorCodes, formatConfigurationErrorTimestamp, } from './config-errors.js';
17
17
  export type { TempivoConfigErrorParameter, TempivoConfigErrorSide, TempivoConfigErrorValue, TempivoConfigurationError, TempivoConfigurationErrors, } from './config-errors.js';
18
18
  export type { TempivoSchedule, TempivoScheduleAlways, TempivoScheduleWeek, TempivoSensorCalibration, TempivoSensorConfiguration, TempivoSensorQr, TempivoSensorSessionType, TempivoTemperatureAlert, TempivoTemperatureChannel, TempivoTemperatureMaxAlert, TempivoTemperatureMinAlert, TempivoTemperatureRangeAlert, TempivoTriggerTransmissionResult, } from './session-types.js';
19
- export { addDeviceFoundListener, connect, clearMeasurementQueue, deleteMeasurements, disconnect, getCalibration, getCellularNetworkStatus, getCellularNetworkStatusJson, getConfiguration, getConfigurationErrors, getProtoConfiguration, isNativeSensorBeaconAvailable, isScanning, requestPermissions, setConfigurationJson, setOperator, startScan, stopScan, triggerTransmission, } from './native-bridge.js';
19
+ export { addDeviceFoundListener, connect, clearMeasurementQueue, deleteMeasurements, disconnect, getCalibration, getCellularNetworkStatus, getCellularNetworkStatusJson, getConfiguration, getConfigurationErrors, getOperator, getProtoConfiguration, isNativeSensorBeaconAvailable, isScanning, requestPermissions, setConfigurationJson, setOperator, startScan, stopScan, triggerTransmission, } from './native-bridge.js';
20
20
  export { cellularNetworkStatusViewFromJson, formatCellularNetworkStatusLabel, formatCellularNetworkStatusLabelWithOperator, formatCellularNetworkStatusSummary, formatCellularServerDetailLabel, isCellularConnectionFullyOk, isCellularRegistrationOk, isCellularServerCommunicationOk, isSimCardOk, isValidCellularIpAddress, normalizeCellularSignalStrengthDbm, parseCellularNetworkStatusJson, parseCellularNetworkStatusObject, resolveCellularStatusTone, } from './cellular-network-status.js';
21
21
  export type { TempivoCellularNetworkStatus, TempivoCellularNetworkStatusView, TempivoCellularStatusLocale, TempivoCellularStatusTone, } from './cellular-network-status.js';
22
- export { PREFERRED_PLMN_BY_COUNTRY_ISO2, PREFERRED_PLMN_FALLBACK, normalizeCountryIso2, resolveSetOperator, } from './plmn-by-country.js';
22
+ export { PREFERRED_PLMN_BY_COUNTRY_ISO2, normalizeCountryIso2, resolveOperatorFromPreferredPlmn, resolveSetOperator, } from './plmn-by-country.js';
23
23
  export type { SetOperatorArg } from './plmn-by-country.js';
24
24
  export { getOperatorNameFromPlmn, PLMN_OPERATOR_LABELS } from './plmn-labels.js';
25
25
  export type { TempivoPlmnOperatorOption } from './plmn-labels.js';
package/dist/index.js CHANGED
@@ -11,8 +11,8 @@ export { calibrationFromExtendedJson, decodeLaboratoryCalibrationTimestamp, form
11
11
  export { BLE_MAX_MEASUREMENT_INTERVAL_MINUTES, BLE_MAX_TRANSMISSION_INTERVAL_SECONDS, BLE_MIN_MEASUREMENT_INTERVAL_MINUTES, BLE_MIN_TRANSMISSION_INTERVAL_SECONDS, DEFAULT_ALERT_HYSTERESIS_C, compileSensorProfileToBleJson, configurationFromApiProfile, parseSensorConfiguration, parseSensorConfigurationFromDevice, parseSensorConfigurationFromDeviceJson, parseSensorConfigurationJson, pickPartnerConfigurationJson, } from './profile.js';
12
12
  export { PROBE_CHANNEL_INDEX_0, alertsRequireProbe, assertProbeAlertsAllowed, channelTypesFromProtoConfig, deviceSupportsProbeFromBeaconReadings, deviceSupportsProbeFromChannelTypes, isTemperatureReadingType, resolveProbeSupported, } from './channel-capability.js';
13
13
  export { configurationErrorsFromProtoConfig, decodeConfigurationError, decodeConfigurationErrorCodes, formatConfigurationErrorTimestamp, } from './config-errors.js';
14
- export { addDeviceFoundListener, connect, clearMeasurementQueue, deleteMeasurements, disconnect, getCalibration, getCellularNetworkStatus, getCellularNetworkStatusJson, getConfiguration, getConfigurationErrors, getProtoConfiguration, isNativeSensorBeaconAvailable, isScanning, requestPermissions, setConfigurationJson, setOperator, startScan, stopScan, triggerTransmission, } from './native-bridge.js';
14
+ export { addDeviceFoundListener, connect, clearMeasurementQueue, deleteMeasurements, disconnect, getCalibration, getCellularNetworkStatus, getCellularNetworkStatusJson, getConfiguration, getConfigurationErrors, getOperator, getProtoConfiguration, isNativeSensorBeaconAvailable, isScanning, requestPermissions, setConfigurationJson, setOperator, startScan, stopScan, triggerTransmission, } from './native-bridge.js';
15
15
  export { cellularNetworkStatusViewFromJson, formatCellularNetworkStatusLabel, formatCellularNetworkStatusLabelWithOperator, formatCellularNetworkStatusSummary, formatCellularServerDetailLabel, isCellularConnectionFullyOk, isCellularRegistrationOk, isCellularServerCommunicationOk, isSimCardOk, isValidCellularIpAddress, normalizeCellularSignalStrengthDbm, parseCellularNetworkStatusJson, parseCellularNetworkStatusObject, resolveCellularStatusTone, } from './cellular-network-status.js';
16
- export { PREFERRED_PLMN_BY_COUNTRY_ISO2, PREFERRED_PLMN_FALLBACK, normalizeCountryIso2, resolveSetOperator, } from './plmn-by-country.js';
16
+ export { PREFERRED_PLMN_BY_COUNTRY_ISO2, normalizeCountryIso2, resolveOperatorFromPreferredPlmn, resolveSetOperator, } from './plmn-by-country.js';
17
17
  export { getOperatorNameFromPlmn, PLMN_OPERATOR_LABELS } from './plmn-labels.js';
18
18
  export { checkProductionDefaults } from './production-defaults.js';
@@ -20,9 +20,9 @@ export declare function deleteMeasurements(): Promise<{
20
20
  ok: boolean;
21
21
  }>;
22
22
  export declare function getCellularNetworkStatusJson(): Promise<string>;
23
+ export declare function getOperator(): Promise<string | null>;
23
24
  export declare function getCellularNetworkStatus(opts?: {
24
25
  locale?: TempivoCellularStatusLocale;
25
- afterServerTrigger?: boolean;
26
26
  }): Promise<TempivoCellularNetworkStatusView>;
27
27
  export declare function setOperator(countryOrAuto: string): Promise<void>;
28
28
  export declare function getCalibration(): Promise<TempivoNativeCalibration>;
@@ -57,9 +57,16 @@ export async function deleteMeasurements() {
57
57
  export async function getCellularNetworkStatusJson() {
58
58
  return (await loadNative()).getCellularNetworkStatusJson();
59
59
  }
60
+ export async function getOperator() {
61
+ return (await loadNative()).getOperator();
62
+ }
60
63
  export async function getCellularNetworkStatus(opts) {
61
- const json = await getCellularNetworkStatusJson();
62
- return cellularNetworkStatusViewFromJson(json, opts);
64
+ const native = await loadNative();
65
+ const [json, operator] = await Promise.all([
66
+ native.getCellularNetworkStatusJson(),
67
+ native.getOperator().catch(() => null),
68
+ ]);
69
+ return cellularNetworkStatusViewFromJson(json, { ...opts, operator });
63
70
  }
64
71
  export async function setOperator(countryOrAuto) {
65
72
  return (await loadNative()).setOperator(countryOrAuto);
@@ -39,9 +39,16 @@ export declare function deleteMeasurements(): Promise<{
39
39
  export declare function getCellularNetworkStatusJson(): Promise<string>;
40
40
  /**
41
41
  * Set PLMN AUTO or preferred operator for a country ISO (`SE`, `NO`, …).
42
+ * Unsupported / unknown ISO → AUTO (no silent Sweden PLMN).
42
43
  * Native merges PLMN into getConfiguration (sanitized). Avoids raw ProtoConfig write.
43
44
  */
44
45
  export declare function setOperator(countryOrAuto: string): Promise<void>;
46
+ /**
47
+ * Read preferred PLMN mode from device config (same field {@link setOperator} writes).
48
+ * Returns `'AUTO'`, a country ISO from the SDK map (`'SE'`, …), or `null` when the device
49
+ * has a custom PLMN outside that map.
50
+ */
51
+ export declare function getOperator(): Promise<string | null>;
45
52
  /** Decoded device ProtoConfig (server, APN, intervals). Requires an open BLE session. */
46
53
  export declare function getProtoConfiguration(): Promise<ProtoConfigObject>;
47
54
  /**
@@ -3,6 +3,7 @@ import { normalizeLaboratoryCalibration, } from './calibration.js';
3
3
  import { configurationErrorsFromProtoConfig, } from './config-errors.js';
4
4
  import { TempivoSensorError } from './errors.js';
5
5
  import { configToJson, mapNativeError, qrToConnectJson } from './native-helpers.js';
6
+ import { resolveOperatorFromPreferredPlmn } from './plmn-by-country.js';
6
7
  import { decodeProtoConfigBase64 } from './proto/decode-proto-config.js';
7
8
  import { partnerConfigFromProtoBase64 } from './proto/partner-from-proto.js';
8
9
  import { partnerConfigToProtoBase64 } from './proto/partner-to-proto.js';
@@ -194,7 +195,10 @@ export async function getCellularNetworkStatusJson() {
194
195
  throw mapNativeError(error);
195
196
  }
196
197
  }
197
- /** Preferred PLMN map (keep in sync with `plmn-by-country.ts`). Inlined for Metro file: links. */
198
+ /**
199
+ * Preferred PLMN map (keep in sync with `plmn-by-country.ts`). Inlined for Metro file: links.
200
+ * Unknown ISO → AUTO (same as {@link resolveSetOperator}).
201
+ */
198
202
  const PREFERRED_PLMN_BY_COUNTRY_ISO2 = {
199
203
  SE: 24001,
200
204
  NO: 24201,
@@ -209,18 +213,26 @@ const PREFERRED_PLMN_BY_COUNTRY_ISO2 = {
209
213
  };
210
214
  /**
211
215
  * Set PLMN AUTO or preferred operator for a country ISO (`SE`, `NO`, …).
216
+ * Unsupported / unknown ISO → AUTO (no silent Sweden PLMN).
212
217
  * Native merges PLMN into getConfiguration (sanitized). Avoids raw ProtoConfig write.
213
218
  */
214
219
  export async function setOperator(countryOrAuto) {
215
220
  const raw = String(countryOrAuto ?? '').trim();
216
- const automatic = !raw || raw.toUpperCase() === 'AUTO';
221
+ let automatic = !raw || raw.toUpperCase() === 'AUTO';
217
222
  let plmn = null;
218
223
  if (!automatic) {
219
224
  const iso = raw.toUpperCase();
220
225
  if (!/^[A-Z]{2}$/.test(iso)) {
221
- throw new TempivoSensorError('unknown', `Invalid country ISO: ${raw}`);
226
+ throw new TempivoSensorError('invalidConfig', `Invalid country ISO: ${raw}`);
227
+ }
228
+ const mapped = PREFERRED_PLMN_BY_COUNTRY_ISO2[iso];
229
+ if (mapped == null) {
230
+ automatic = true;
231
+ plmn = null;
232
+ }
233
+ else {
234
+ plmn = mapped;
222
235
  }
223
- plmn = PREFERRED_PLMN_BY_COUNTRY_ISO2[iso] ?? 24001;
224
236
  }
225
237
  try {
226
238
  const native = getNative();
@@ -233,6 +245,26 @@ export async function setOperator(countryOrAuto) {
233
245
  throw mapNativeError(error);
234
246
  }
235
247
  }
248
+ /**
249
+ * Read preferred PLMN mode from device config (same field {@link setOperator} writes).
250
+ * Returns `'AUTO'`, a country ISO from the SDK map (`'SE'`, …), or `null` when the device
251
+ * has a custom PLMN outside that map.
252
+ */
253
+ export async function getOperator() {
254
+ try {
255
+ const native = getNative();
256
+ if (typeof native.getOperator !== 'function') {
257
+ throw new TempivoSensorError('runtimeUnavailable', 'Native getOperator is unavailable in this build.');
258
+ }
259
+ const raw = await withTimeout(native.getOperator(), 45_000, 'getOperator');
260
+ const automatic = raw?.automatic !== false;
261
+ const plmn = raw?.plmn == null || raw.plmn === undefined ? null : Number(raw.plmn);
262
+ return resolveOperatorFromPreferredPlmn(automatic || plmn == null || !Number.isFinite(plmn), Number.isFinite(plmn) ? plmn : null);
263
+ }
264
+ catch (error) {
265
+ throw mapNativeError(error);
266
+ }
267
+ }
236
268
  function withTimeout(promise, ms, label) {
237
269
  return new Promise((resolve, reject) => {
238
270
  const id = setTimeout(() => {
@@ -3,12 +3,21 @@
3
3
  * Keep in sync with app `src/lib/default-plmn-by-country.ts`.
4
4
  */
5
5
  export declare const PREFERRED_PLMN_BY_COUNTRY_ISO2: Record<string, number>;
6
- export declare const PREFERRED_PLMN_FALLBACK = 24001;
7
6
  export declare function normalizeCountryIso2(code: string | null | undefined): string | null;
8
7
  /** `AUTO` or ISO2 → PLMN mode for BLE. */
9
8
  export type SetOperatorArg = 'AUTO' | string;
9
+ /**
10
+ * Resolve `setOperator` arg.
11
+ * Unknown / unsupported country ISO → `AUTO` (never invent a default PLMN).
12
+ * Invalid shape (not empty, not AUTO, not two letters) → throws.
13
+ */
10
14
  export declare function resolveSetOperator(arg: SetOperatorArg): {
11
15
  mode: 'AUTO' | 'VALUE';
12
16
  plmn?: number;
13
17
  countryIso?: string;
14
18
  };
19
+ /**
20
+ * Map device preferred PLMN (from config) to a {@link setOperator} arg.
21
+ * Automatic / missing → `AUTO`. Known preferred PLMN → ISO2. Other custom PLMN → `null`.
22
+ */
23
+ export declare function resolveOperatorFromPreferredPlmn(automatic: boolean, plmn: number | null | undefined): string | null;
@@ -14,7 +14,6 @@ export const PREFERRED_PLMN_BY_COUNTRY_ISO2 = {
14
14
  IT: 22201,
15
15
  ES: 21401,
16
16
  };
17
- export const PREFERRED_PLMN_FALLBACK = 24001;
18
17
  export function normalizeCountryIso2(code) {
19
18
  if (code == null || typeof code !== 'string')
20
19
  return null;
@@ -23,6 +22,11 @@ export function normalizeCountryIso2(code) {
23
22
  return null;
24
23
  return t;
25
24
  }
25
+ /**
26
+ * Resolve `setOperator` arg.
27
+ * Unknown / unsupported country ISO → `AUTO` (never invent a default PLMN).
28
+ * Invalid shape (not empty, not AUTO, not two letters) → throws.
29
+ */
26
30
  export function resolveSetOperator(arg) {
27
31
  const raw = String(arg ?? '').trim();
28
32
  if (!raw || raw.toUpperCase() === 'AUTO')
@@ -30,6 +34,29 @@ export function resolveSetOperator(arg) {
30
34
  const iso = normalizeCountryIso2(raw);
31
35
  if (!iso)
32
36
  throw new Error(`Invalid country ISO: ${raw}`);
33
- const plmn = PREFERRED_PLMN_BY_COUNTRY_ISO2[iso] ?? PREFERRED_PLMN_FALLBACK;
37
+ const plmn = PREFERRED_PLMN_BY_COUNTRY_ISO2[iso];
38
+ if (plmn == null)
39
+ return { mode: 'AUTO' };
34
40
  return { mode: 'VALUE', plmn, countryIso: iso };
35
41
  }
42
+ /** Reverse of {@link PREFERRED_PLMN_BY_COUNTRY_ISO2} (first ISO wins when several share a PLMN). */
43
+ const COUNTRY_ISO2_BY_PREFERRED_PLMN = (() => {
44
+ const out = {};
45
+ for (const [iso, plmn] of Object.entries(PREFERRED_PLMN_BY_COUNTRY_ISO2)) {
46
+ if (out[plmn] == null)
47
+ out[plmn] = iso;
48
+ }
49
+ return out;
50
+ })();
51
+ /**
52
+ * Map device preferred PLMN (from config) to a {@link setOperator} arg.
53
+ * Automatic / missing → `AUTO`. Known preferred PLMN → ISO2. Other custom PLMN → `null`.
54
+ */
55
+ export function resolveOperatorFromPreferredPlmn(automatic, plmn) {
56
+ if (automatic || plmn == null)
57
+ return 'AUTO';
58
+ const n = Number(plmn);
59
+ if (!Number.isFinite(n))
60
+ return null;
61
+ return COUNTRY_ISO2_BY_PREFERRED_PLMN[n] ?? null;
62
+ }
@@ -13,22 +13,22 @@ export { calibrationFromExtendedJson, decodeLaboratoryCalibrationTimestamp, form
13
13
  export { BLE_MAX_MEASUREMENT_INTERVAL_MINUTES, BLE_MAX_TRANSMISSION_INTERVAL_SECONDS, BLE_MIN_MEASUREMENT_INTERVAL_MINUTES, BLE_MIN_TRANSMISSION_INTERVAL_SECONDS, DEFAULT_ALERT_HYSTERESIS_C, compileSensorProfileToBleJson, configurationFromApiProfile, parseSensorConfiguration, parseSensorConfigurationFromDevice, parseSensorConfigurationFromDeviceJson, parseSensorConfigurationJson, pickPartnerConfigurationJson, } from './profile.js';
14
14
  export type { TempivoSchedule, TempivoScheduleAlways, TempivoScheduleWeek, TempivoSensorCalibration, TempivoSensorConfiguration, TempivoSensorQr, TempivoSensorSessionType, TempivoTemperatureAlert, TempivoTemperatureChannel, TempivoTemperatureMaxAlert, TempivoTemperatureMinAlert, TempivoTemperatureRangeAlert, TempivoTriggerTransmissionResult, } from './session-types.js';
15
15
  export type { TempivoDeviceFoundSubscription, TempivoNativeCalibration, TempivoNativeMeasurement, TempivoNativePermissionResponse, TempivoNativePermissionStatus, TempivoNativeQr, TempivoNativeTelemetry, TempivoSensorBeaconDevice, } from './native-types.js';
16
- import { getCellularNetworkStatusJson as getCellularNetworkStatusJsonNative, deleteMeasurements as deleteMeasurementsNative, setOperator as setOperatorNative, isNativeSensorBeaconAvailable, requestPermissions, startScan, stopScan, isScanning, addDeviceFoundListener, connect, disconnect, getConfiguration, getConfigurationErrors, setConfigurationJson, triggerTransmission, clearMeasurementQueue, getCalibration, getProtoConfiguration } from './native-module.js';
16
+ import { getCellularNetworkStatusJson as getCellularNetworkStatusJsonNative, getOperator as getOperatorNative, deleteMeasurements as deleteMeasurementsNative, setOperator as setOperatorNative, isNativeSensorBeaconAvailable, requestPermissions, startScan, stopScan, isScanning, addDeviceFoundListener, connect, disconnect, getConfiguration, getConfigurationErrors, setConfigurationJson, triggerTransmission, clearMeasurementQueue, getCalibration, getProtoConfiguration } from './native-module.js';
17
17
  import { type TempivoCellularNetworkStatusView, type TempivoCellularStatusLocale } from './cellular-network-status.js';
18
18
  export { isNativeSensorBeaconAvailable, requestPermissions, startScan, stopScan, isScanning, addDeviceFoundListener, connect, disconnect, getConfiguration, getConfigurationErrors, setConfigurationJson, triggerTransmission, clearMeasurementQueue, getCalibration, getProtoConfiguration, };
19
19
  /** Raw GATT JSON (no label formatting). */
20
20
  export declare const getCellularNetworkStatusJson: typeof getCellularNetworkStatusJsonNative;
21
21
  export declare const deleteMeasurements: typeof deleteMeasurementsNative;
22
22
  export declare const setOperator: typeof setOperatorNative;
23
+ export declare const getOperator: typeof getOperatorNative;
23
24
  /**
24
- * GATT cellular status with short partner labels.
25
+ * GATT cellular status with short partner labels + preferred `operator` (`AUTO` / ISO).
25
26
  * Composed here (static imports) so Metro file: links do not dynamic-import label helpers.
26
27
  */
27
28
  export declare function getCellularNetworkStatus(opts?: {
28
29
  locale?: TempivoCellularStatusLocale;
29
- afterServerTrigger?: boolean;
30
30
  }): Promise<TempivoCellularNetworkStatusView>;
31
- export { PREFERRED_PLMN_BY_COUNTRY_ISO2, resolveSetOperator, } from './plmn-by-country.js';
31
+ export { PREFERRED_PLMN_BY_COUNTRY_ISO2, resolveOperatorFromPreferredPlmn, resolveSetOperator, } from './plmn-by-country.js';
32
32
  export { cellularNetworkStatusViewFromJson, formatCellularNetworkStatusLabel, formatCellularNetworkStatusLabelWithOperator, formatCellularNetworkStatusSummary, formatCellularServerDetailLabel, isCellularConnectionFullyOk, isCellularRegistrationOk, isCellularServerCommunicationOk, isSimCardOk, isValidCellularIpAddress, normalizeCellularSignalStrengthDbm, parseCellularNetworkStatusJson, parseCellularNetworkStatusObject, resolveCellularStatusTone, } from './cellular-network-status.js';
33
33
  export type { TempivoCellularNetworkStatus, TempivoCellularNetworkStatusView, TempivoCellularStatusLocale, TempivoCellularStatusTone, } from './cellular-network-status.js';
34
34
  export { getOperatorNameFromPlmn, PLMN_OPERATOR_LABELS } from './plmn-labels.js';
@@ -9,22 +9,26 @@ export { isMissingAdvertisementFirmware, sensorModelFromFirmware } from './model
9
9
  export { buildSensorQrConnectJson, draftSensorQrConnectJsonFromScanDevice, formatSensorReadingLine, mergeScanTargetIntoSensorQr, normalizeSensorStickerModel, parseSensorQrForConnect, pinFromSensorQrRecord, resolveSensorBleConnectCredentialsFromPin, sensorQrConnectJsonFromLoose, sensorQrSerialMatchesSelected, serialHexFromSensorQrScan, tryParseSensorQrJson, } from './connect-helpers.js';
10
10
  export { calibrationFromExtendedJson, decodeLaboratoryCalibrationTimestamp, formatLaboratoryCalibration, normalizeLaboratoryCalibration, } from './calibration.js';
11
11
  export { BLE_MAX_MEASUREMENT_INTERVAL_MINUTES, BLE_MAX_TRANSMISSION_INTERVAL_SECONDS, BLE_MIN_MEASUREMENT_INTERVAL_MINUTES, BLE_MIN_TRANSMISSION_INTERVAL_SECONDS, DEFAULT_ALERT_HYSTERESIS_C, compileSensorProfileToBleJson, configurationFromApiProfile, parseSensorConfiguration, parseSensorConfigurationFromDevice, parseSensorConfigurationFromDeviceJson, parseSensorConfigurationJson, pickPartnerConfigurationJson, } from './profile.js';
12
- import { getCellularNetworkStatusJson as getCellularNetworkStatusJsonNative, deleteMeasurements as deleteMeasurementsNative, setOperator as setOperatorNative, isNativeSensorBeaconAvailable, requestPermissions, startScan, stopScan, isScanning, addDeviceFoundListener, connect, disconnect, getConfiguration, getConfigurationErrors, setConfigurationJson, triggerTransmission, clearMeasurementQueue, getCalibration, getProtoConfiguration, } from './native-module.js';
12
+ import { getCellularNetworkStatusJson as getCellularNetworkStatusJsonNative, getOperator as getOperatorNative, deleteMeasurements as deleteMeasurementsNative, setOperator as setOperatorNative, isNativeSensorBeaconAvailable, requestPermissions, startScan, stopScan, isScanning, addDeviceFoundListener, connect, disconnect, getConfiguration, getConfigurationErrors, setConfigurationJson, triggerTransmission, clearMeasurementQueue, getCalibration, getProtoConfiguration, } from './native-module.js';
13
13
  import { cellularNetworkStatusViewFromJson, } from './cellular-network-status.js';
14
14
  export { isNativeSensorBeaconAvailable, requestPermissions, startScan, stopScan, isScanning, addDeviceFoundListener, connect, disconnect, getConfiguration, getConfigurationErrors, setConfigurationJson, triggerTransmission, clearMeasurementQueue, getCalibration, getProtoConfiguration, };
15
15
  /** Raw GATT JSON (no label formatting). */
16
16
  export const getCellularNetworkStatusJson = getCellularNetworkStatusJsonNative;
17
17
  export const deleteMeasurements = deleteMeasurementsNative;
18
18
  export const setOperator = setOperatorNative;
19
+ export const getOperator = getOperatorNative;
19
20
  /**
20
- * GATT cellular status with short partner labels.
21
+ * GATT cellular status with short partner labels + preferred `operator` (`AUTO` / ISO).
21
22
  * Composed here (static imports) so Metro file: links do not dynamic-import label helpers.
22
23
  */
23
24
  export async function getCellularNetworkStatus(opts) {
24
- const json = await getCellularNetworkStatusJsonNative();
25
- return cellularNetworkStatusViewFromJson(json, opts);
25
+ const [json, operator] = await Promise.all([
26
+ getCellularNetworkStatusJsonNative(),
27
+ getOperatorNative().catch(() => null),
28
+ ]);
29
+ return cellularNetworkStatusViewFromJson(json, { ...opts, operator });
26
30
  }
27
- export { PREFERRED_PLMN_BY_COUNTRY_ISO2, resolveSetOperator, } from './plmn-by-country.js';
31
+ export { PREFERRED_PLMN_BY_COUNTRY_ISO2, resolveOperatorFromPreferredPlmn, resolveSetOperator, } from './plmn-by-country.js';
28
32
  export { cellularNetworkStatusViewFromJson, formatCellularNetworkStatusLabel, formatCellularNetworkStatusLabelWithOperator, formatCellularNetworkStatusSummary, formatCellularServerDetailLabel, isCellularConnectionFullyOk, isCellularRegistrationOk, isCellularServerCommunicationOk, isSimCardOk, isValidCellularIpAddress, normalizeCellularSignalStrengthDbm, parseCellularNetworkStatusJson, parseCellularNetworkStatusObject, resolveCellularStatusTone, } from './cellular-network-status.js';
29
33
  export { getOperatorNameFromPlmn, PLMN_OPERATOR_LABELS } from './plmn-labels.js';
30
34
  export { configurationErrorsFromProtoConfig, decodeConfigurationError, decodeConfigurationErrorCodes, formatConfigurationErrorTimestamp, } from './config-errors.js';
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tempivo/sensor-beacon",
3
- "version": "1.1.9",
3
+ "version": "1.1.10",
4
4
  "description": "Tempivo sensor BLE SDK. Native scan and GATT for Expo, React Native, Android, and iOS. Node helpers decode advertisements and parse QR or config (no radio).",
5
5
  "homepage": "https://app.tempivo.com/integrations",
6
6
  "author": "Tempivo",