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.plist let’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']).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

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.

Libraries

rtl_ir_control