bluetooth_thermometer 0.9.3
bluetooth_thermometer: ^0.9.3 copied to clipboard
A Flutter package for connecting to and reading temperature from ThermoWorks Bluetooth thermometers (Thermapen Blue, TempTest Blue).
bluetooth_thermometer #
A Flutter package for connecting to and reading temperature data from Bluetooth Low Energy (BLE) thermometers, specifically designed for ThermoWorks devices including Thermapen Blue and TempTest Blue.
⚠️ Platform Support #
This package is mobile-only (Android and iOS). Desktop platforms (Windows, Linux, macOS) and web are not supported as they require Bluetooth Low Energy (BLE) hardware that is typically only available on mobile devices.
✨ Features #
- 🔍 Device Discovery: Scan for nearby Bluetooth thermometers with signal strength
- 🔗 Smart Connection: Auto-connect to previously used devices with signal prioritization
- 🌡️ Real-time Temperature: Subscribe to live temperature readings
- 🔋 Battery Monitoring: Automatic low battery warnings and level tracking (probably works; needs further testing)
- ⚙️ Device Settings: Read/write thermometer settings (brightness, units, etc.)
- 🎯 Button Detection: Detect physical button presses on supported devices
- 🔐 Smart Permissions: Lazy permission requests with intelligent error handling
- 📊 Analytics System: Comprehensive event logging for debugging Bluetooth issues
- 🔄 Auto-Disconnect: Prevent battery drain with configurable idle timeouts
- 🔌 Manual Shutdown: Option to manually shut down connected devices
- 🔍 Deeper Scan: Extended scan mode for devices that don't appear in quick scans
Getting started #
Prerequisites #
- Flutter SDK (see
environment.sdkinpubspec.yaml) - Android device with Bluetooth support (Android 6.0+)
- iOS device with Bluetooth support (iOS 12.0+)
Installation #
Add this package to your pubspec.yaml:
dependencies:
bluetooth_thermometer: ^0.9.3
See the installation tab for the latest version.
⚠️ Required Permissions #
Your app MUST add these permissions:
Android (android/app/src/main/AndroidManifest.xml):
<!-- Android 12+ -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesFeature="true"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
<!-- Android 11 and older -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
iOS (ios/Runner/Info.plist):
<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app needs Bluetooth access to connect to Bluetooth thermometers.</string>
See the repository's main README.md for detailed integration guidance.
⚡ Quick Start (2 minutes) #
The fastest path to a working thermometer UI: add the dependency and permissions above, then:
final client = ThermometerClient();
await client.initializeAndWarmupBluetooth();
// In build:
SimpleThermometerDemo(client: client)
That's it. The widget handles connection, scanning, temperature display, and button capture. See Usage for the full page example and manual integration.
Analytics & Observability #
This package includes a comprehensive analytics system for tracking permission flows and debugging Bluetooth connectivity issues. The analytics system provides detailed event logging with timestamps, making it easy to understand complex asynchronous permission flows.
Analytics Features #
- Semantic Event Categories: Automatic categorization via
EventCategoryenum (happyPath, connections, scanning, deviceOps, lifecycle, system, errors, permissions, denialPath, critical) - Framework-Agnostic Core: Core analytics system has zero Flutter dependencies, works in any Dart environment
- UI-Specific Display: Separate
DisplayEventCategoryenum provides Flutter icons/colors for widgets - Device Shutdown Analysis: Comprehensive tracking of intentional vs unintentional shutdowns with detailed explanations
- Multiple Handlers: In-memory for development, local storage for production, custom handlers for Sentry/Firebase
- Export Functionality: JSON export for sharing with support teams
- Zero-Performance Impact: Silent failure if no handler is configured
Event Category Architecture #
The analytics system uses a clean separation between core categorization and UI display:
Core EventCategory Enum (analytics_event.dart)
- Framework-agnostic categorization logic
- Zero Flutter dependencies
- Used by analytics handlers and event processing
- Available in any Dart environment (CLI tools, servers, etc.)
UI DisplayEventCategory Enum (display_event_category.dart)
- Flutter-specific visual properties (icons, colors)
- Used only by UI widgets for display
- Provides rich visual filtering in
AnalyticsLogViewer
This separation ensures the analytics core remains lightweight and framework-independent while providing rich UI experiences.
Share Logs #
Share analytics logs in multiple formats for different use cases:
Quick Sharing (JSON)
import 'package:share_plus/share_plus.dart';
final shareButtonKey = GlobalKey();
IconButton(
key: shareButtonKey,
icon: Icon(Icons.share),
onPressed: () => Share.share(
BluetoothAnalytics.exportData(),
subject: 'Analytics Logs (JSON)',
sharePositionOrigin: shareButtonKey.calculateShareAnchorRect(),
),
);
Advanced Sharing with Multiple Formats
import 'package:share_plus/share_plus.dart';
import 'package:bluetooth_thermometer/bluetooth_thermometer.dart';
void shareAnalytics(BuildContext context, GlobalKey shareKey, AnalyticsExportFormat format) {
Share.share(
context.getAnalyticsData(format),
subject: context.getAnalyticsSubject(format),
sharePositionOrigin: shareKey.calculateShareAnchorRect(),
);
}
// Usage
shareAnalytics(context, shareButtonKey, AnalyticsExportFormat.csv); // For spreadsheets
shareAnalytics(context, shareButtonKey, AnalyticsExportFormat.text); // For emails
shareAnalytics(context, shareButtonKey, AnalyticsExportFormat.summary); // For quick overview
📊 Analytics Events Reference #
For a complete catalog of all analytics events with detailed explanations and usage examples, see the Analytics Events Reference. This document includes:
- Complete event catalog organized by category
- Detailed shutdown detection analysis (intentional vs unintentional)
- Implementation examples for popular analytics services
- Event priority levels and usage patterns
Export Formats Available
| Format | Use Case | Sample Output |
|---|---|---|
| JSON | Complete data, API integration | {"events": [...], "categories": {...}} |
| Text | Human-readable, emails, logs | Formatted report with timestamps |
| CSV | Spreadsheet analysis, data processing | timestamp,key,extraInfo,category,priority |
| Summary | Quick overview, dashboards | Compact statistics and insights |
Anchor Positioning Utility
The calculateShareAnchorRect() extension handles the confusing anchor positioning:
extension AnalyticsSharing on GlobalKey {
Rect? calculateShareAnchorRect() {
final box = currentContext?.findRenderObject() as RenderBox?;
if (box == null) return null;
final position = box.localToGlobal(Offset.zero);
final size = box.size;
return Rect.fromLTWH(position.dx, position.dy, size.width, size.height);
}
}
Professional Sharing Example
class AnalyticsSharingWidget extends StatelessWidget {
final GlobalKey _shareKey = GlobalKey();
@override
Widget build(BuildContext context) {
return PopupMenuButton<AnalyticsExportFormat>(
child: IconButton(
key: _shareKey,
icon: Icon(Icons.share),
onPressed: () {}, // Handled by popup menu
),
onSelected: (format) => _shareAnalytics(context, format),
itemBuilder: (context) => AnalyticsExportFormat.values.map((format) {
return PopupMenuItem(
value: format,
child: Text('Share as ${format.displayName}'),
);
}).toList(),
);
}
void _shareAnalytics(BuildContext context, AnalyticsExportFormat format) {
Share.share(
context.getAnalyticsData(format),
subject: context.getAnalyticsSubject(format),
sharePositionOrigin: _shareKey.calculateShareAnchorRect(),
);
}
}
Use AnalyticsLogViewer widget for a complete UI to view, filter, and export logs:
AnalyticsLogViewer(
title: 'Analytics Logs',
showExportButton: true,
showClearButton: true,
)
Third-Party Analytics Integrations #
For complete working examples of integrating with popular analytics services, see the Custom Analytics Handlers Guide.
Quick Examples
Sentry (3 minutes):
// Add: sentry_flutter: ^latest
class SentryAnalyticsHandler extends AnalyticsHandler {
@override
void trackEvent(AnalyticsEvent event) {
Sentry.addBreadcrumb(Breadcrumb(message: event.key, data: {'extra': event.extraInfo}));
}
}
Firebase (5 minutes):
// Add: firebase_analytics: ^latest
class FirebaseAnalyticsHandler extends AnalyticsHandler {
@override
void trackEvent(AnalyticsEvent event) {
FirebaseAnalytics.instance.logEvent(name: event.key, parameters: {'extra': event.extraInfo});
}
}
REST API (10 minutes):
class RestApiAnalyticsHandler extends AnalyticsHandler {
@override
void trackEvent(AnalyticsEvent event) {
http.post(Uri.parse('your-api-endpoint'), body: {'event': event.key, 'data': event.extraInfo});
}
}
Then register your handler:
BluetoothAnalytics.initialize(MyAnalyticsHandler());
Permission-Aware Components #
The PermissionAwareBluetoothHandler widget provides intelligent permission management:
- Lazy Initialization: Permissions only requested when user initiates Bluetooth scanning
- Smart Dialogs: Uses
smart_permissionpackage for battle-tested permission flows - On-Resume Detection: Automatically detects when permissions are granted in OS settings
- Comprehensive Error Handling: Clear user guidance for all permission states
📚 Technical Documentation #
- Analytics Events Reference: Complete catalog of all analytics events with detailed explanations
- Permission Flow Architecture: Detailed technical breakdown of the permission-aware Bluetooth scanning system
- Analytics System Design: Complete analytics implementation guide with examples
- Analytics Implementation Details: Deep dive into the analytics handler architecture
Smart Connectivity Features #
This package includes advanced connection management features designed to improve user experience and reduce common Bluetooth bugs in real-world kitchen environments.
⚡ Auto-Connect #
The autoConnect() method provides a "zero-touch" connection experience by automatically finding and connecting to the most relevant device.
How it works:
- Memory: Remembers the last 5 successfully connected devices (LRU cache).
- Signal Strength: Scans for known devices and picks the one with the strongest signal (RSSI).
- Safety: Only connects to previously trusted devices, preventing accidental connections to other nearby thermometers.
Benefits:
- Eliminates Selection Fatigue: Users don't need to select their device every time they open the app.
- Reduces "Wrong Device" Errors: Prioritizes the device physically closest to the tablet/phone.
🔋 Auto-Disconnect #
The package supports patterns for automatic disconnection to prevent battery drain and connection conflicts.
Best Practices Implementation:
- Idle Timer: Disconnect after a period of inactivity (e.g., 45 seconds). This saves the thermometer's battery.
- Background Disconnect: Disconnect immediately when the app goes to the background. This releases the Bluetooth lock, allowing other apps or devices to connect if needed.
Real-World Impact:
- Prevents "Device Busy" Bugs: By releasing the connection when idle, it prevents the common issue where a thermometer is "hijacked" by a backgrounded app, making it invisible to other users.
- Extends Hardware Life: significantly reduces battery consumption on the Thermapen Blue.
Usage #
See the /example folder for a complete example app.
Quick Start: Embedded Demo #
Use SimpleThermometerDemo when you want a ready-to-use temperature-taking UI with minimal code. Ideal for embedding thermometer functionality into an existing app (e.g. kiq_mobile) or getting up and running quickly without a full app shell.
The widget is self-contained: it wraps content in PermissionAwareBluetoothHandler and ThermometerLifecycleManager, and handles connection status, connect prompt, temperature display, Take Temp button, and physical button capture.
Minimal usage (2 lines setup + 1 in build):
final client = ThermometerClient();
await client.initializeAndWarmupBluetooth();
// In build:
SimpleThermometerDemo(client: client)
Adding to a flutter create project without removing anything: ~35 lines of Dart code. Add a demo page (~25 lines) that holds the client and disposes it, plus a navigation button (~5 lines) and the import. Plus 1 line in pubspec.yaml. The widget handles everything else.
// Example: Add ThermometerDemoPage and navigate from your home screen
class ThermometerDemoPage extends StatefulWidget {
const ThermometerDemoPage({super.key});
@override
State<ThermometerDemoPage> createState() => _ThermometerDemoPageState();
}
class _ThermometerDemoPageState extends State<ThermometerDemoPage> {
late final ThermometerClient _client;
@override
void initState() {
super.initState();
_client = ThermometerClient();
_client.initializeAndWarmupBluetooth();
}
@override
void dispose() {
_client.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Thermometer Demo')),
body: SimpleThermometerDemo(client: _client),
);
}
}
Basic usage (manual integration) #
import 'package:flutter/foundation.dart';
import 'package:bluetooth_thermometer/bluetooth_thermometer.dart';
// Create a client
final client = ThermometerClient();
// Initialize Bluetooth (required - triggers iOS permission dialog)
await client.initializeAndWarmupBluetooth();
// Listen for discovered devices
client.devices.listen((devices) {
debugPrint('Found ${devices.length} devices');
});
// Scan for devices
await client.scanForDevices(timeout: Duration(seconds: 10));
// Connect to a device
client.connect(devices.first);
// Listen for temperature readings
client.temperatureStream.listen((temperature) {
debugPrint('Temperature: $temperature');
});
// Clean up when done
client.dispose();
Pre-built Widgets #
| Widget | Use Case |
|---|---|
ThermometerSelector |
Status chip + bottom sheet; shows Retry if not initialized |
BluetoothNotInitializedWidget |
Error state with optional Retry button |
SimpleThermometerDemo |
Ready-to-use temperature UI (connect, take temp, button capture) |
AsyncFilledButton |
Button that shows centered progress indicator when loading |
PermissionAwareBluetoothHandler |
Lazy permission requests with smart dialogs |
ThermometerLifecycleManager |
Auto-connect on resume, idle disconnect |
Device Compatibility #
⚠️ IMPORTANT: This package is designed specifically for ThermoWorks thermometers including Thermapen Blue and TempTest Blue.
Supported Features by Device #
| Feature | Thermapen Blue | TempTest Blue | Other Devices |
|---|---|---|---|
| Temperature Reading | ✅ Full Support | ✅ Full Support | ⚠️ May work if using same UUIDs |
| Settings Read/Write | ✅ Full Support | ✅ Full Support | ❌ NOT SUPPORTED |
| Button Press Detection | ✅ Full Support | ✅ Full Support | ❌ NOT SUPPORTED |
| Battery Level Monitoring | ⚠️ Probably works; needs further testing | ⚠️ Probably works; needs further testing | ❌ NOT SUPPORTED |
Both Thermapen Blue and TempTest Blue support all features. Other Thermapen models (Thermapen ONE, Classic, etc.) and other manufacturers' thermometers may not support advanced features like settings and battery monitoring.
Safety Note: Reading settings (readSettings()) appears safe with no known limits - you can probably read as many times as needed. Only writing settings has known limits (~10,000-100,000 write cycles). The only known impact of reading is very minor battery usage (negligible for normal usage).
Manufacturer Documentation #
ThermoWorks Official Resources #
- Thermapen Blue Product Page: https://www.thermoworks.com/thermapen-blue
- TempTest Blue Product Page: https://www.thermoworks.com/temptest-blue
- ThermoWorks Support: https://www.thermoworks.com/support
- ThermoWorks Contact: https://www.thermoworks.com/contact
Bluetooth Specifications #
This package uses proprietary service and characteristic UUIDs specific to ThermoWorks thermometers:
- Service UUID:
45544942-4c55-4554-4845-524db87ad700 - Temperature Characteristic:
45544942-4c55-4554-4845-524db87ad701 - Settings Characteristic:
45544942-4c55-4554-4845-524db87ad709(ThermoWorks devices) - Command Characteristic:
45544942-4c55-4554-4845-524db87ad705(ThermoWorks devices) - Battery Characteristic:
00002A19-0000-1000-8000-00805F9B34FB(Standard BLE)
⚠️ Note: These UUIDs are not publicly documented by ThermoWorks. They were reverse-engineered from the device's BLE implementation.
Testing #
Integration Tests #
The demo app includes comprehensive integration tests for different testing scenarios:
UI Workflow Test (integration_test/ui_workflow_test.dart):
- Tests Bluetooth UI interactions and state management
- Validates modal dialogs, scanning workflow, and proper UI responses
- Works on any Android device (no Bluetooth hardware required)
Live Device Temperature Test (integration_test/live_device_temperature_test.dart):
- Complete end-to-end test with physical Thermapen Blue hardware
- Verifies app launch, permissions, device discovery, connection, and live temperature reading
- Requires physical Android device + Thermapen Blue thermometer
ADB Wrapper Scripts:
run_live_device_test.sh: Automated testing with real hardware- Grants permissions via ADB and runs the live device test
Additional information #
This package is designed specifically for ThermoWorks thermometers (Thermapen Blue and TempTest Blue) and uses their proprietary service and characteristic UUIDs. It may work with other BLE thermometers that use the same service structure for temperature reading, but this is not guaranteed. Advanced features (settings read/write, button press detection, battery monitoring) are only supported on ThermoWorks devices. Battery monitoring probably works but needs further testing.