CLI (`cli.py`)
├── discovery and BLE lifecycle (`client.py`) ──► Bleak ──► host Bluetooth stack
├── pure notification parsing (`parser.py`)
└── measurement model and CSV sink (`measurement.py`)
The parser has no Bluetooth dependency. The client accepts an injectable client factory and sleep function, allowing async lifecycle behavior to be tested without a radio. The CLI translates user input into those library interfaces.
- The user supplies a device identifier from
scan. UT383BTClientopensBleakClientas an async context manager.- The client subscribes to Data Out notifications.
- A background task writes the measurement request to Data In at the requested interval.
- Notifications are copied to immutable bytes, optionally exposed for diagnostics, parsed, timestamped, and delivered to the callback.
- Malformed notifications are logged and ignored.
- On timeout, cancellation, or error, the request task is cancelled, notifications stop, and the async context disconnects.
- Live hardware is never required in automated tests.
- Device identifiers are runtime inputs, not repository configuration.
- The GATT write response mode is always explicit.
- Physical units are integer lux because that is the observed payload.
- Raw payloads remain available on measurements for diagnostics but are not written to CSV.