rtl_ir_control
A Flutter package for setup and controlling infrared (IR) devices via Bluetooth. This package
leverages
esp_blufi and flutter_blue_plus to set up and manage Bluetooth Low Energy (BLE) connections and
download ir code from api call to device.
Features
- Device scanning and connection management using BLE.
- Blufi setup for BW8459 devices.
- Downloading IR code for device control.
Dependencies
This package relies on the following external packages:
esp_blufi: For BLE communication with ESP-based devices.flutter_blue_plus: For Bluetooth Low Energy operations on Android and iOS.
Setup
Permission
To use this package, you must configure Bluetooth permissions for your Flutter app. Below are the steps for Android and iOS.
Android
Add the following to android/app/src/main/AndroidManifest.xml:
<!-- Tell Google Play Store that your app uses Bluetooth LE
Set android:required="true" if bluetooth is necessary -->
<uses-feature android:name="android.hardware.bluetooth_le" android:required="false" />
<!-- New Bluetooth permissions in Android 12
https://developer.android.com/about/versions/12/features/bluetooth-permissions -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" /><uses-permission
android:name="android.permission.BLUETOOTH_CONNECT" /><uses-permission
android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- legacy for Android 11 or lower -->
<uses-permission android:maxSdkVersion="30"
android:name="android.permission.BLUETOOTH" /><uses-permission android:maxSdkVersion="30"
android:name="android.permission.BLUETOOTH_ADMIN" />
<!-- legacy for Android 9 or lower -->
<uses-permission android:maxSdkVersion="28"
android:name="android.permission.ACCESS_COARSE_LOCATION" />
iOS
- In the
ios/Runner/Info.plistlet’s add:
<dict>
<!-- Required for Bluetooth usage on iOS 13 and later; explains why the app needs Bluetooth access -->
<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app requires Bluetooth to scan and connect to devices.</string>
<!-- Legacy key for Bluetooth usage on iOS 12 and earlier; included for backward compatibility -->
<key>NSBluetoothPeripheralUsageDescription</key>
<string>This app requires Bluetooth to scan and connect to devices.</string>
</dict>
- To use BLE in app background, add the following to your Info.plist
<dict>
<key>UIBackgroundModes</key>
<array>
<string>bluetooth-central</string>
</array>
</dict>
Note: Ensure that Bluetooth and location permissions have been granted for the app.
How to use
1. Scan Devices
Scan for BW8459 devices using a name prefix filter.
- Both BLUETOOTH_SCAN and ACCESS_FINE_LOCATION permissions are required for Bluetooth scanning on Android.
import 'package:rtl_ir_control/rtl_ir_control.dart';
void scanDevices() {
RtlIrControl().startScan(nameFilter: ['BW8459', 'BLUFI', 'RM2']).listen((
List<RTLBluetoothScanRecord> scanResults,) {
print('Discovered devices (${scanResults.length}):');
for (var result in scanResults) {
print('Device Name: ${result.device.name} ${result.device.advName}');
}
});
}
2. Setup Device
Checks the device's configuration status, prepares it for Wi-Fi setup by using enterSetupMode()
function
Future<void> verifyAndSetup(String id) async {
try {
// Initialize the device using the provided ID
final device = RtlIrControl().getDevice(id);
// Establish connection to the device
await device.connect();
// Create handler for device operations
final handler = BW8459DeviceHandler(device);
// OPTIONAL - check device is already configured
bool isConfigured = await handler.isConfigured();
// enter setup mode programmatically
await handler.enterSetupMode();
// IMPORTANT: release connection for blufi setup
await device.disconnect();
await setupDevice(id);
} catch (e) {
print("Setup failed: $e");
}
}
Connect to a device, scan for Wi-Fi networks, and configure Wi-Fi credentials.
Note: Ensure the device is in pairing mode before starting the setup process. Otherwise, it may appear disconnected or result in a connection timeout.
import 'package:rtl_ir_control/rtl_ir_control.dart';
Future<void> setupDevice(String id) async {
final blufi = RTLBlufiSetupUtils();
try {
// Set device ID form scan
await blufi.setCurrentId(id);
// Establish connection
await blufi.connect();
// Scan for Wi-Fi networks
var wifiList = await blufi.scanWifiList();
// Select Wi-Fi (replace with your UI logic)
var ssid = await selectWifi(wifiList);
// Get password (replace with your UI logic)
var password = await inputPassword(ssid);
// Provision Wi-Fi credentials
await blufi.configProvision(ssid: ssid, password: password);
print("Setup Completed");
} catch (e) {
print("Setup failed: $e");
}
}
// Placeholder UI functions
Future<String> selectWifi(List<String> wifiList) async {
return wifiList.first; // Replace with actual selection logic
}
Future<String> inputPassword(String ssid) async {
return "your_password"; // Replace with actual input logic
}
3. Read Device Information
Read the device information to get IR data from the API.
import 'package:rtl_ir_control/rtl_ir_control.dart';
Future<void> readDeviceInfo(String id) async {
try {
// Initialize the device using the provided ID
final device = RtlIrControl().getDevice(id);
// Establish connection to the device
await device.connect();
// Create handler for device operations
final handler = BW8459DeviceHandler(device);
var masterKey = await handler.readMasterKey();
var macAddress = await handler.readMacAddress();
var firmwareVersion = await handler.readFirmwareVersion();
var hardwareVersion = await handler.readHardwareVersion();
var modelNumber = await handler.readRemoteModel();
// display device information
print('''
Master Key: $masterKey
MAC Address: $macAddress
Firmware Version: $firmwareVersion
Hardware Version: $hardwareVersion
Model Number: $modelNumber
''');
// Disconnect after the action is completed
await device.disconnect();
} catch (e) {
print("Read device info failed: $e");
} finally {
// Ensure device is properly disconnected
await device.disconnect();
}
}
4. Decoding API Response to IR Codeset
Convert the API response to an IR codeset for downloading.
void decodeIrCodeset(String jsonResponse) {
try {
// Decode codeset from API response
RTLIrCodeset codeset = RTLIrCodeset.fromApiJson(jsonResponse);
print("Successfully decoded IR codeset: ${codeset.toString()}");
} catch (e) {
print("Failed to decode IR codeset: $e");
}
}
5. Download IR Codeset
Download an IR codeset to a device.
import 'package:rtl_ir_control/rtl_ir_control.dart';
Future<void> downloadCodes(String id, RTLIrCodeset codeset) async {
try {
// Get device instance
var device = RtlIrControl().getDevice(id);
// Connect to device
await device.connect();
// Initialize handler
final handler = BW8459DeviceHandler(device);
// Download codeset with progress updates
handler.downloadCodeFromCodeset(
channel: 1,
brandName: "LG",
codeNum: "81",
codeset: codeset,
).listen((progress) => print("Download progress: $progress%"),
onDone: () {
print("Download completed");
device.disconnect();
},
onError: (e) {
print("Download failed: $e");
device.disconnect();
},
);
} catch (e) {
print("Code download failed: $e");
}
}
6. Delete IR Codeset
Delete IR codes with channel.
import 'package:rtl_ir_control/rtl_ir_control.dart';
Future<void> deleteCodes(String id, int channel) async {
try {
// Get device instance
var device = RtlIrControl().getDevice(id);
// Connect to device
await device.connect();
// Initialize handler
final handler = BW8459DeviceHandler(device);
// Delete IR Codeset with channel
await handler.deleteCode(channel);
// Disconnect after the action is completed
await device.disconnect();
} catch (e) {
print("Delete code failed: $e");
}
}
Custom Device Handler
Extend the functionality of rtl_ir_control by creating a custom DeviceHandler tailored to your
Bluetooth device.
import 'package:rtl_ir_control/rtl_ir_control.dart';
class CustomDeviceHandler extends GeneralDeviceHandler {
// Custom UUIDs for your device's BLE service and characteristic
static final RTLBluetoothUuid customServiceUuid =
RTLBluetoothUuid('00001800-0000-1000-8000-00805f9b34fb'); // Generic Access service
static final RTLBluetoothUuid customCharacteristicUuid =
RTLBluetoothUuid('00002a00-0000-1000-8000-00805f9b34fb'); // Device Name characteristic
// Constructor takes an RTLBluetoothDevice instance
CustomDeviceHandler(super.rtlBluetoothDevice);
// Read raw data from the device as a byte list
Future<List<int>> readCustomData() async {
final value = await rtlBluetoothDevice.readData(
customServiceUuid,
customCharacteristicUuid,
);
return value; // Returns raw bytes (e.g., for further processing)
}
// Send a custom command to the device
Future<void> sendCustomCommand(List<int> data) async {
await rtlBluetoothDevice.sendCommand(
customServiceUuid,
customCharacteristicUuid,
data, // Byte list representing your command
);
}
// Stream progress for a custom operation (e.g., firmware update)
Stream<int> performCustomOperation(int param) async* {
for (var i = 0; i <= 100; i += 20) {
await Future.delayed(Duration(milliseconds: 500)); // Simulate async work
yield i; // Emit progress percentage
}
// Replace with your device's specific operation logic
}
}
Once you've created your CustomDeviceHandler, use the following example to call its methods:
// Usage
Future<void> useCustomHandler(String deviceId) async {
try {
// Retrieve and connect to the device
var device = RtlIrControl().getDevice(deviceId);
await device.connect();
// Instantiate your custom handler
var handler = CustomDeviceHandler(device);
// Read data (raw bytes)
List<int> data = await handler.readCustomData();
print("Custom data (bytes): $data");
// Send a sample command
await handler.sendCustomCommand([0x01, 0x02, 0x03]);
print("Command sent successfully");
// Monitor a custom operation's progress
handler.performCustomOperation(42).listen(
(progress) => print("Operation progress: $progress%"),
onDone: () => print("Operation completed"),
);
} catch (e) {
print("Error in custom handler usage: $e");
}
}
Notes
-
Handle exceptions (e.g., RTLBluetoothException) for errors like timeouts or permission issues.
-
The package is tailored for BW8459 devices; adapt for other devices as needed.
-
Use a real API response for apiJson in production.