ble_device_simulator
Build and test Bluetooth LE health and fitness apps without hardware.
Simulated heart-rate straps, blood pressure cuffs, thermometers, pulse oximeters, power meters, foot pods and scales that send the exact bytes real devices send, following the Bluetooth SIG specs.
- Write tests for BLE code: in CI, on every developer's laptop, with no device in a drawer somewhere
- Reproduce edge cases on demand: a slipped strap, a fever, low oxygen, a flat battery, a dropped connection
- Deterministic: the same seed always gives the same readings, and simulated time only moves when you say so
- Real bytes: every notification is checked in this package's tests by parsing it with ble_gatt_parser
- Pure Dart: works in Flutter widget tests, plain Dart tests and on the web
final strap = SimulatedHeartRateMonitor()..connect();
strap.notifications(GattCharacteristicIds.heartRateMeasurement).listen((bytes) {
print(GattParser.parse('2a37', bytes)); // HeartRateMeasurement(bpm: 74, ...)
});
strap.tick(); // one second of simulated time
strap.skinContact = false; // the strap slips off
strap.batteryLevel = 5; // battery warning
strap.disconnect(); // out of range
Install
dart pub add dev:ble_device_simulator
Add it as a regular dependency instead if you also want simulated devices in a demo or debug build.
Devices
| Class | Service | Sends | Edge cases |
|---|---|---|---|
SimulatedHeartRateMonitor |
Heart Rate (0x180D) | bpm and RR-intervals every second | skinContact = false, custom bpm range |
SimulatedBloodPressureMonitor |
Blood Pressure (0x1810) | A reading when you call measure() |
Custom systolic and diastolic ranges |
SimulatedThermometer |
Health Thermometer (0x1809) | Ear temperature every second | fever = true |
SimulatedPulseOximeter |
Pulse Oximeter (0x1822) | SpO2, pulse and perfusion index | lowOxygen = true |
SimulatedPowerMeter |
Cycling Power (0x1818) | Watts and crank revolutions, for cadence | Custom power and rpm ranges |
SimulatedRunningPod |
Running Speed and Cadence (0x1814) | Speed, cadence and distance | Custom speed range |
SimulatedWeightScale |
Weight Scale (0x181D) | A measurement when you call weigh() |
Optional BMI and height |
Every device also has Device Information (manufacturer, model, firmware) and Battery (0x180F), with read(), connect(), disconnect(), connectionState, batteryLevel and services.
Testing a Flutter app
Put a small interface between your app and your BLE plugin, and use the simulator in tests:
abstract class HeartRateSource {
Stream<List<int>> get measurements;
}
/// In the app: flutter_blue_plus, flutter_reactive_ble or your own plugin.
class BluetoothHeartRateSource implements HeartRateSource {
BluetoothHeartRateSource(this.characteristic);
final BluetoothCharacteristic characteristic;
@override
Stream<List<int>> get measurements => characteristic.onValueReceived;
}
/// In tests and demos.
class SimulatedHeartRateSource implements HeartRateSource {
SimulatedHeartRateSource(this.device);
final SimulatedHeartRateMonitor device;
@override
Stream<List<int>> get measurements =>
device.notifications(GattCharacteristicIds.heartRateMeasurement);
}
testWidgets('shows a warning when the strap slips', (tester) async {
final strap = SimulatedHeartRateMonitor()..connect();
await tester.pumpWidget(HeartRateScreen(source: SimulatedHeartRateSource(strap)));
strap.tick();
await tester.pump();
expect(find.textContaining('bpm'), findsOneWidget);
strap.skinContact = false;
strap.tick();
await tester.pump();
expect(find.text('Check your strap'), findsOneWidget);
});
Time
Simulated time only moves when you call tick(), which advances one second and sends that second's notifications synchronously. That keeps tests fast and predictable. For demos, start(interval: ...) ticks on a timer: a shorter interval runs simulated time faster. Call stop() or dispose() when you're done.
Encoders
The devices are built on GattEncoder, which you can also use directly to build any measurement by hand:
final bytes = GattEncoder.bloodPressure(
systolic: 142,
diastolic: 91,
pulseRate: 88,
timestamp: DateTime(2026, 1, 1, 8),
);
It covers heart rate, blood pressure, temperature, pulse oximetry, weight, cycling speed and cadence, cycling power, running speed and cadence, battery level and strings, plus the IEEE 11073 SFLOAT and FLOAT number formats.
About the author
Built by Chandra ObulaReddy, a Flutter architect who has shipped Bluetooth LE and wearable integrations for safety, healthcare and fitness products. See also ble_gatt_parser, which parses the bytes these devices send.
Libraries
- ble_device_simulator
- Simulated Bluetooth LE health and fitness devices that send spec-correct GATT notifications, for building and testing apps without hardware.