@condorinst/mobile-bridge 0.22.0 → 0.23.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/AGENTS.md +133 -34
- package/CHANGELOG.md +175 -0
- package/README.md +30 -481
- package/contract/bridge-contract.json +32 -21
- package/dist/cli.cjs +126 -29
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +126 -29
- package/dist/cli.js.map +1 -1
- package/dist/dev-harness.global.js +388 -55
- package/dist/dev.cjs +389 -55
- package/dist/dev.cjs.map +1 -1
- package/dist/dev.d.cts +239 -9
- package/dist/dev.d.ts +239 -9
- package/dist/dev.js +388 -55
- package/dist/dev.js.map +1 -1
- package/dist/index.cjs +30 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +154 -26
- package/dist/index.d.ts +154 -26
- package/dist/index.js +29 -8
- package/dist/index.js.map +1 -1
- package/docs/agent-guide.md +9 -265
- package/docs/bridge-contract.md +313 -26
- package/docs/client-guide.md +34 -400
- package/docs/device-testing.md +234 -0
- package/docs/pilot-onboarding.md +41 -115
- package/docs/shell-integration.md +292 -0
- package/docs/troubleshooting.md +19 -11
- package/examples/minimal-home/README.md +2 -2
- package/examples/minimal-home/package.json +1 -1
- package/examples/minimal-home/src/main.ts +23 -3
- package/llms-full.txt +764 -865
- package/llms.txt +13 -15
- package/package.json +1 -1
package/dist/dev.d.cts
CHANGED
|
@@ -105,20 +105,167 @@ interface DeviceUserConfigItem {
|
|
|
105
105
|
* — but only when that `serialNo` appears once in `devicesConfig`. A device may
|
|
106
106
|
* have no telemetry yet: render it with empty readings, never drop it.
|
|
107
107
|
*
|
|
108
|
-
* **
|
|
109
|
-
*
|
|
108
|
+
* **Below `nativeVersion` 0.18.0 it is not guaranteed to answer**: a request
|
|
109
|
+
* sent while the app's background service starts is lost, and a failed lookup
|
|
110
|
+
* gets no reply. From 0.18.0 the app re-sends the lost request itself and
|
|
111
|
+
* answers a failure with empty arrays and `error`. Either way, use
|
|
112
|
+
* `BridgeClient.requestUserDevices()` instead of `send('userDevice')`; see
|
|
113
|
+
* `docs/troubleshooting.md`.
|
|
110
114
|
*
|
|
111
115
|
* The app enforces no device cap. A commercial rule currently limits a user to
|
|
112
116
|
* two linked devices, but that can change with no signal in this contract — read
|
|
113
117
|
* the length of the array rather than hard-coding it.
|
|
114
118
|
*/
|
|
119
|
+
/**
|
|
120
|
+
* Why the device list could not be loaded (`UserDevicePayload.error`, from
|
|
121
|
+
* `nativeVersion` 0.18.0):
|
|
122
|
+
* - `NO_DEVICES` — the server says the account has no linked device;
|
|
123
|
+
* - `NETWORK` — no answer from the server (offline, timeout) or a 5xx;
|
|
124
|
+
* - `SESSION` — no session, or the server ended it;
|
|
125
|
+
* - `UNKNOWN` — anything else. Treat an unrecognised value like `UNKNOWN`.
|
|
126
|
+
*/
|
|
127
|
+
type UserDeviceErrorCode = 'NO_DEVICES' | 'NETWORK' | 'SESSION' | 'UNKNOWN' | (string & {});
|
|
115
128
|
interface UserDevicePayload {
|
|
116
129
|
/** Epoch milliseconds of the snapshot. */
|
|
117
130
|
lastUpdate: number;
|
|
131
|
+
/**
|
|
132
|
+
* Present only when the list could not be loaded; the three arrays are then
|
|
133
|
+
* empty. From `nativeVersion` 0.18.0 — older apps send no reply at all in
|
|
134
|
+
* that case. `BridgeClient.requestUserDevices()` turns it into a status.
|
|
135
|
+
*/
|
|
136
|
+
error?: UserDeviceErrorCode;
|
|
118
137
|
devicesConfig: DeviceConfigItem[];
|
|
119
138
|
devicesTelemetry: DeviceTelemetryItem[];
|
|
120
139
|
devicesUserConfig: DeviceUserConfigItem[];
|
|
121
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Raw light-sensor channels, under the names the backend uses in
|
|
143
|
+
* `user_cmd=log` — the same vocabulary for history and for real time.
|
|
144
|
+
* Relative values with no physical unit (as are the `wavelength*` channels);
|
|
145
|
+
* only `photopic` / `melanopic` (and `lux_*` in the history) are lux.
|
|
146
|
+
*
|
|
147
|
+
* Present from **nativeVersion 0.16.0**. A bundle compiled against this SDK can
|
|
148
|
+
* run on an older native app that does not emit them: check `nativeVersion`
|
|
149
|
+
* from the handshake ack before depending on them, or read defensively. They
|
|
150
|
+
* are deliberately **not** optional in the type — optional would hide the
|
|
151
|
+
* version skew instead of making it explicit.
|
|
152
|
+
*
|
|
153
|
+
* ⚠️ `nativeVersion` only reaches the client from **SDK 0.17.0**: up to
|
|
154
|
+
* `0.16.x` the shell answered the client handshake with `shellVersion` alone,
|
|
155
|
+
* so this check was impossible to satisfy from inside the iframe.
|
|
156
|
+
*
|
|
157
|
+
* `null` means **the channel does not exist on this hardware generation**,
|
|
158
|
+
* never "the reading was zero" — `0.0` is a valid reading. Coercing `null` to
|
|
159
|
+
* `0` fabricates data.
|
|
160
|
+
*/
|
|
161
|
+
type LightSensorsRawChannels = {
|
|
162
|
+
light_sensor_f1: number;
|
|
163
|
+
light_sensor_f2: number;
|
|
164
|
+
light_sensor_f3: number;
|
|
165
|
+
light_sensor_f4: number;
|
|
166
|
+
light_sensor_f5: number;
|
|
167
|
+
light_sensor_f6: number;
|
|
168
|
+
light_sensor_f7: number;
|
|
169
|
+
light_sensor_f8: number;
|
|
170
|
+
/** Total light (`visible` on the device wire format). */
|
|
171
|
+
light_sensor_clear: number;
|
|
172
|
+
/** Near infrared. */
|
|
173
|
+
light_sensor_nir: number;
|
|
174
|
+
};
|
|
175
|
+
/**
|
|
176
|
+
* A real-time reading from an ActLumus gen1 (`type: 'actlumus'`).
|
|
177
|
+
*
|
|
178
|
+
* Mirrors `LightSensorsActlumus1GraphicData.toMap()` in `condor_interactors`.
|
|
179
|
+
* Gen1 has neither the tristimulus channels nor flicker detect — those four
|
|
180
|
+
* keys arrive as `null`, present in the schema so that its shape does not
|
|
181
|
+
* change between hardware generations.
|
|
182
|
+
*/
|
|
183
|
+
type LightSensorsActlumus1Data = LightSensorsRawChannels & {
|
|
184
|
+
/** When the phone received the reading: epoch ms (UTC), phone clock. */
|
|
185
|
+
time: number;
|
|
186
|
+
wavelength390: number;
|
|
187
|
+
wavelength415: number;
|
|
188
|
+
wavelength445: number;
|
|
189
|
+
wavelength480: number;
|
|
190
|
+
wavelength515: number;
|
|
191
|
+
wavelength555: number;
|
|
192
|
+
wavelength590: number;
|
|
193
|
+
wavelength630: number;
|
|
194
|
+
wavelength680: number;
|
|
195
|
+
wavelength750: number;
|
|
196
|
+
melanopic: number;
|
|
197
|
+
photopic: number;
|
|
198
|
+
light_sensor_z: null;
|
|
199
|
+
light_sensor_y: null;
|
|
200
|
+
light_sensor_x: null;
|
|
201
|
+
light_sensor_fd: null;
|
|
202
|
+
};
|
|
203
|
+
/**
|
|
204
|
+
* A real-time reading from an ActLumus Plus / gen2 (`type: 'actlumus2'`).
|
|
205
|
+
*
|
|
206
|
+
* Mirrors `LightSensorsActlumus2GraphicData.toMap()` in `condor_interactors`.
|
|
207
|
+
*
|
|
208
|
+
* ⚠️ The wavelengths are **not** the gen1 ones: 14 of them (395…800) against
|
|
209
|
+
* 10 (390…750), with no value in common. A chart that reads the gen1 list on a
|
|
210
|
+
* gen2 device finds `undefined` in every series and renders empty. Always
|
|
211
|
+
* discriminate on `type`.
|
|
212
|
+
*
|
|
213
|
+
* `light_sensor_z/_y/_x` are the CIE 1931 tristimulus values — Z in the blue,
|
|
214
|
+
* Y in the green, X in the red.
|
|
215
|
+
*/
|
|
216
|
+
type LightSensorsActlumus2Data = LightSensorsRawChannels & {
|
|
217
|
+
/** When the phone received the reading: epoch ms (UTC), phone clock. */
|
|
218
|
+
time: number;
|
|
219
|
+
wavelength395: number;
|
|
220
|
+
wavelength426: number;
|
|
221
|
+
wavelength457: number;
|
|
222
|
+
wavelength488: number;
|
|
223
|
+
wavelength520: number;
|
|
224
|
+
wavelength551: number;
|
|
225
|
+
wavelength582: number;
|
|
226
|
+
wavelength613: number;
|
|
227
|
+
wavelength644: number;
|
|
228
|
+
wavelength675: number;
|
|
229
|
+
wavelength707: number;
|
|
230
|
+
wavelength738: number;
|
|
231
|
+
wavelength769: number;
|
|
232
|
+
wavelength800: number;
|
|
233
|
+
melanopic: number;
|
|
234
|
+
photopic: number;
|
|
235
|
+
light_sensor_z: number;
|
|
236
|
+
light_sensor_y: number;
|
|
237
|
+
light_sensor_x: number;
|
|
238
|
+
/** Flicker detect. Not part of the spectrum matrix. */
|
|
239
|
+
light_sensor_fd: number;
|
|
240
|
+
};
|
|
241
|
+
/**
|
|
242
|
+
* Real-time light-sensor stream. Shape:
|
|
243
|
+
* `{ serialNo, deviceId?, type, data }`.
|
|
244
|
+
*
|
|
245
|
+
* `deviceId` is the server id of the device and is **optional**: the app only
|
|
246
|
+
* sends it when more than one device on the account shares that `serialNo` (an
|
|
247
|
+
* ActLumus and an ActLumus Plus can). With a unique `serialNo` it is absent, so
|
|
248
|
+
* keep matching on `serialNo` in that case. Present from `nativeVersion` 0.17.0.
|
|
249
|
+
*
|
|
250
|
+
* A union discriminated by `type`, which is the `DeviceType.name` on the Dart
|
|
251
|
+
* side. `data` carries **both the derived spectrum and the raw channels it came
|
|
252
|
+
* from** — before 0.16.0 only the spectrum crossed the bridge, and a probe
|
|
253
|
+
* looking up a raw channel by name read `undefined`.
|
|
254
|
+
*
|
|
255
|
+
* This stream only flows while real-time is on for that device; it is off by
|
|
256
|
+
* default. See the `realTime` command.
|
|
257
|
+
*/
|
|
258
|
+
type DeviceLightSensorsPayload = {
|
|
259
|
+
serialNo: number;
|
|
260
|
+
deviceId?: number;
|
|
261
|
+
type: 'actlumus';
|
|
262
|
+
data: LightSensorsActlumus1Data;
|
|
263
|
+
} | {
|
|
264
|
+
serialNo: number;
|
|
265
|
+
deviceId?: number;
|
|
266
|
+
type: 'actlumus2';
|
|
267
|
+
data: LightSensorsActlumus2Data;
|
|
268
|
+
};
|
|
122
269
|
/**
|
|
123
270
|
* Parameters of the `sensorLog` command.
|
|
124
271
|
*
|
|
@@ -139,8 +286,8 @@ interface SensorLogQueryPayload {
|
|
|
139
286
|
/** Echoed back in the reply — correlates concurrent requests. */
|
|
140
287
|
requestId: string;
|
|
141
288
|
serialNo: number;
|
|
142
|
-
/**
|
|
143
|
-
bodyPart?:
|
|
289
|
+
/** Omit for no filter. */
|
|
290
|
+
bodyPart?: 'wrist' | 'necklace';
|
|
144
291
|
/** 1..1000 (backend default is 10). Outside the range: `INVALID_LIMIT`. */
|
|
145
292
|
limit?: number;
|
|
146
293
|
startingAfter?: string;
|
|
@@ -165,6 +312,11 @@ interface SensorLogItem {
|
|
|
165
312
|
/** `'YYYY-MM-DD HH:MM:SS'` in **device-local time**, not UTC. */
|
|
166
313
|
log_time: string;
|
|
167
314
|
log_type: number;
|
|
315
|
+
/**
|
|
316
|
+
* Where the device was set to be worn (its `deviceBodyPart`) when this log was
|
|
317
|
+
* uploaded — a setting, not a detection. Whether it was actually worn is
|
|
318
|
+
* `off_wrist`.
|
|
319
|
+
*/
|
|
168
320
|
body_part: 'wrist' | 'necklace' | null;
|
|
169
321
|
/** Degrees Celsius, already converted by the backend. */
|
|
170
322
|
temperature: number | null;
|
|
@@ -197,6 +349,11 @@ interface SensorLogItem {
|
|
|
197
349
|
act_data_pim?: number | null;
|
|
198
350
|
act_data_tat?: number | null;
|
|
199
351
|
act_data_zcm?: number | null;
|
|
352
|
+
/**
|
|
353
|
+
* The device's raw wear flag, and **the name misleads**: `1` = worn, `0` = not
|
|
354
|
+
* worn (on its dock). Treat any value other than `1` as not worn, and `null`
|
|
355
|
+
* as unknown.
|
|
356
|
+
*/
|
|
200
357
|
off_wrist?: number | null;
|
|
201
358
|
cap_sensor_1?: number | null;
|
|
202
359
|
cap_sensor_2?: number | null;
|
|
@@ -251,6 +408,12 @@ interface SystemStatusPayload {
|
|
|
251
408
|
internet: boolean;
|
|
252
409
|
permission: boolean;
|
|
253
410
|
hasDevices: boolean;
|
|
411
|
+
/**
|
|
412
|
+
* `true` when `hasDevices` is the server's answer; `false` when the app could
|
|
413
|
+
* not ask (no internet, session) — then `hasDevices: false` does **not** mean
|
|
414
|
+
* "no device". Absent below `nativeVersion` 0.18.0: treat absent as unknown.
|
|
415
|
+
*/
|
|
416
|
+
hasDevicesKnown?: boolean;
|
|
254
417
|
/**
|
|
255
418
|
* Optional permissions not yet granted, as `AppPermission` names
|
|
256
419
|
* (e.g. `["notification", "backgroundLocation"]`). Additive and
|
|
@@ -299,6 +462,28 @@ declare function twoDevices(now?: number): UserDevicePayload;
|
|
|
299
462
|
*/
|
|
300
463
|
declare function sharedSerial(now?: number): UserDevicePayload;
|
|
301
464
|
declare function systemStatus(overrides?: Partial<SystemStatusPayload>): SystemStatusPayload;
|
|
465
|
+
/**
|
|
466
|
+
* One `deviceLightSensors` reading for a device, shaped by its generation
|
|
467
|
+
* (`modelId` `'actlumus_plus'` → `type: 'actlumus2'`). The harness emits one
|
|
468
|
+
* per second while `realTime` is on for that device. Values are plausible,
|
|
469
|
+
* not measured.
|
|
470
|
+
*/
|
|
471
|
+
declare function lightSensorsReading(device: Pick<DeviceConfigItem, 'serialNo' | 'deviceId' | 'modelId'>, epochMs?: number, withDeviceId?: boolean): DeviceLightSensorsPayload;
|
|
472
|
+
/**
|
|
473
|
+
* What the simulated history contains. The default: 14 days up to now, one log
|
|
474
|
+
* per `logPeriodS`, worn all day except the dock hour 07:00–08:00 (local), no
|
|
475
|
+
* gaps.
|
|
476
|
+
*/
|
|
477
|
+
interface HistoryOptions {
|
|
478
|
+
/** Days of history before now. Default 14. */
|
|
479
|
+
days?: number;
|
|
480
|
+
/** Local hours (0–23) the device sits on its dock: `off_wrist` 0, no light. Default `[7]`. */
|
|
481
|
+
dockHours?: number[];
|
|
482
|
+
/** Local hours (0–23) with no log at all, every day (device off, or memory full). */
|
|
483
|
+
gapHours?: number[];
|
|
484
|
+
/** Local dates (`YYYY-MM-DD`) with no log at all. */
|
|
485
|
+
gapDates?: string[];
|
|
486
|
+
}
|
|
302
487
|
/**
|
|
303
488
|
* Answers a `sensorLog` query with the backend's paging semantics:
|
|
304
489
|
* - no range: the most recent `limit` logs; older pages through `endingBefore`;
|
|
@@ -311,6 +496,9 @@ declare function sensorLogPage(query: SensorLogQueryPayload, opts?: {
|
|
|
311
496
|
now?: number;
|
|
312
497
|
periodS?: number;
|
|
313
498
|
deviceId?: number;
|
|
499
|
+
/** `body_part` of every log: the device's `deviceBodyPart`. Default `'wrist'`. */
|
|
500
|
+
bodyPart?: SensorLogItem['body_part'];
|
|
501
|
+
history?: HistoryOptions;
|
|
314
502
|
}): SensorLogPayload;
|
|
315
503
|
|
|
316
504
|
/**
|
|
@@ -547,7 +735,7 @@ interface MockAdapter {
|
|
|
547
735
|
*/
|
|
548
736
|
|
|
549
737
|
/** Native schema version the harness reports by default. */
|
|
550
|
-
declare const HARNESS_NATIVE_VERSION = "0.
|
|
738
|
+
declare const HARNESS_NATIVE_VERSION = "0.18.0";
|
|
551
739
|
interface Scenario {
|
|
552
740
|
/** Shown in the harness toolbar. */
|
|
553
741
|
description: string;
|
|
@@ -566,12 +754,30 @@ interface Scenario {
|
|
|
566
754
|
*/
|
|
567
755
|
serviceStartMs: number;
|
|
568
756
|
/**
|
|
569
|
-
*
|
|
570
|
-
*
|
|
757
|
+
* The device lookup fails with this code (`NO_DEVICES`, `NETWORK`, …). From
|
|
758
|
+
* nativeVersion 0.18.0 the app answers `userDevice` with empty arrays and
|
|
759
|
+
* `error`; below it, the bundle gets no reply at all.
|
|
571
760
|
*/
|
|
572
|
-
|
|
761
|
+
userDeviceError?: string;
|
|
573
762
|
/** When set, every `sensorLog` reply fails with this `error`. */
|
|
574
763
|
sensorLogError?: string;
|
|
764
|
+
/** The Nth `sensorLog` request (1-based) fails once with `Connection failed`. */
|
|
765
|
+
sensorLogFailOnRequest?: number;
|
|
766
|
+
/** What the simulated history contains — see `HistoryOptions`. */
|
|
767
|
+
history?: HistoryOptions;
|
|
768
|
+
/**
|
|
769
|
+
* After this many ms the first device starts a BLE session (a
|
|
770
|
+
* `deviceConnection` with `isConnect: true`). Only `default` sets it; absent,
|
|
771
|
+
* no device connects by itself (a scenario file can pass `null`).
|
|
772
|
+
*/
|
|
773
|
+
firstSessionAfterMs?: number | null;
|
|
774
|
+
/**
|
|
775
|
+
* The pipeline stage at which every simulated BLE session fails, e.g.
|
|
776
|
+
* `'get_data_log'`: `deviceNotification` then ends with `<stage>_error`
|
|
777
|
+
* instead of `pipeline_done_notification`. Without internet the sessions
|
|
778
|
+
* fail at `'send_data_log'` anyway.
|
|
779
|
+
*/
|
|
780
|
+
sessionFailAt?: string;
|
|
575
781
|
/** Log period of the devices, in seconds (programmable; default 60). */
|
|
576
782
|
logPeriodS: number;
|
|
577
783
|
systemStatus: SystemStatusPayload;
|
|
@@ -613,7 +819,12 @@ interface HarnessLogEntry {
|
|
|
613
819
|
kind: 'sent' | 'reply' | 'dropped' | 'info';
|
|
614
820
|
command?: string;
|
|
615
821
|
key?: string;
|
|
822
|
+
/** One line for humans; arguments are shortened. */
|
|
616
823
|
detail: string;
|
|
824
|
+
/** `sent`: the command's arguments, complete. */
|
|
825
|
+
args?: Record<string, unknown>;
|
|
826
|
+
/** `reply`: the payload the bundle received, complete. */
|
|
827
|
+
payload?: unknown;
|
|
617
828
|
}
|
|
618
829
|
interface DevHarnessOptions {
|
|
619
830
|
/** The bundle's `index.html`, self-contained, as a string. */
|
|
@@ -639,6 +850,23 @@ interface DevHarness {
|
|
|
639
850
|
readonly log: readonly HarnessLogEntry[];
|
|
640
851
|
/** Emits a native event to the bundle, as the app would. */
|
|
641
852
|
emit(key: string, payload: unknown): void;
|
|
853
|
+
/**
|
|
854
|
+
* Merges into the simulated `systemStatus` and emits it, as the app does on
|
|
855
|
+
* every change — e.g. `setStatus({ internet: false })`.
|
|
856
|
+
*/
|
|
857
|
+
setStatus(patch: Partial<SystemStatusPayload>): void;
|
|
858
|
+
/**
|
|
859
|
+
* Starts or ends the BLE session of one device: emits `deviceConnection`
|
|
860
|
+
* (when the bundle subscribed to it with `add`) and starts or stops its
|
|
861
|
+
* `deviceLightSensors` stream if `realTime` is on.
|
|
862
|
+
*/
|
|
863
|
+
setConnected(deviceId: number, connected: boolean): void;
|
|
864
|
+
/**
|
|
865
|
+
* How the app's confirmation dialog (`syncTime`, `deleteMemory`, `logout`)
|
|
866
|
+
* is answered: `'ask'` (an in-page dialog, the default), `'accept'` or
|
|
867
|
+
* `'decline'` — the last two for scripted tests.
|
|
868
|
+
*/
|
|
869
|
+
setConfirmations(mode: 'ask' | 'accept' | 'decline'): void;
|
|
642
870
|
/**
|
|
643
871
|
* Presses the OS back button. Emits `navigationBack` only when the bundle
|
|
644
872
|
* last reported `canGoBack: true`, as the app does; otherwise the app keeps
|
|
@@ -658,6 +886,8 @@ declare function createSimulatedNative(adapter: MockAdapter, scenario: Scenario,
|
|
|
658
886
|
}): {
|
|
659
887
|
readonly log: readonly HarnessLogEntry[];
|
|
660
888
|
pressBack(): void;
|
|
889
|
+
setStatus(patch: Partial<SystemStatusPayload>): void;
|
|
890
|
+
setConnected(deviceId: number, connected: boolean): void;
|
|
661
891
|
dispose(): void;
|
|
662
892
|
};
|
|
663
893
|
/**
|
|
@@ -680,4 +910,4 @@ declare const CANVAS_POLICY: ShellPolicy;
|
|
|
680
910
|
/** CSP of the app shell, inherited by the client's `srcdoc` iframe. */
|
|
681
911
|
declare const CANVAS_CSP: string;
|
|
682
912
|
|
|
683
|
-
export { CANVAS_CSP, CANVAS_POLICY, DEFAULT_LOG_PERIOD_S, DEV_HARNESS_MARKER, type DevHarness, type DevHarnessOptions, HARNESS_NATIVE_VERSION, type HarnessLogEntry, SCENARIOS, type Scenario, type ScenarioName, createDevHarness, createSimulatedNative, formatLocalTimestamp, parseLocalTimestamp, resolveScenario, sensorLogPage, sharedSerial, systemStatus, twoDevices };
|
|
913
|
+
export { CANVAS_CSP, CANVAS_POLICY, DEFAULT_LOG_PERIOD_S, DEV_HARNESS_MARKER, type DevHarness, type DevHarnessOptions, HARNESS_NATIVE_VERSION, type HarnessLogEntry, type HistoryOptions, SCENARIOS, type Scenario, type ScenarioName, createDevHarness, createSimulatedNative, formatLocalTimestamp, lightSensorsReading, parseLocalTimestamp, resolveScenario, sensorLogPage, sharedSerial, systemStatus, twoDevices };
|