flutter_ota 0.1.8 flutter_ota: ^0.1.8 copied to clipboard
A Flutter package for OTA updating firmware of ESP32 using Bluetooth Low Energy (BLE).
// ignore_for_file: use_build_context_synchronously
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:flutter_ota/ota_package.dart';
import 'package:get/get.dart';
import 'controllers/bleUart_controller.dart';
import 'controllers/ble_servicescontroller.dart';
// This class represents a page for OTA (Over-The-Air) firmware updates.
class OtaUpdatePage extends StatelessWidget {
// Controllers for handling user input and displaying received data.
final TextEditingController commandController = TextEditingController();
final TextEditingController receivedController = TextEditingController();
// Controller for managing Bluetooth Low Energy UART communication.
final BleUartController bleUartController = BleUartController();
// Flags to control UI elements.
bool showProgressDialog = true; // Flag to show progress dialog.
bool firmwareFileSelected = false; // Flag indicating whether firmware file is selected.
// Bluetooth Characteristics for data and control.
BluetoothCharacteristic? dataUuid;
BluetoothCharacteristic? controlUuid;
// File path for the firmware binary file.
String? binfile;
@override
Widget build(BuildContext context) {
// Completer to handle asynchronous completion for confirming Bluetooth disconnection.
Completer<bool> _completer = Completer<bool>();
// Variable to store the custom path for firmware updates.
String? customPath;
// Build method returns a WillPopScope widget to handle the back button press.
return WillPopScope(
// onWillPop callback is triggered when the back button is pressed.
onWillPop: () async {
// Show a confirmation dialog before navigating back.
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Disconnect and Go Back?'),
content: const Text(
'Do you want to disconnect from the available Bluetooth devices and go back to the scanning page?',
),
actions: [
// Disconnect button pressed.
ElevatedButton(
style: ElevatedButton.styleFrom(
// Add any custom styling for the button if needed.
),
onPressed: () async {
// Close the dialog, navigate to scanning page, and complete with true.
Navigator.pop(context);
Navigator.pushReplacementNamed(context, '/scanningpage');
_completer.complete(true);
},
child: const Text('Disconnect'),
),
// Cancel button pressed.
ElevatedButton(
style: ElevatedButton.styleFrom(
// Add any custom styling for the button if needed.
),
onPressed: () {
// Close the dialog and complete with false.
Navigator.pop(context);
_completer.complete(false);
},
child: const Text('Cancel'),
),
],
),
);
// Wait for the completion of user's choice before allowing the back navigation.
return await _completer.future;
},
child: Container(
// Container with a gradient background.
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [
Color.fromRGBO(125, 186, 236, 1),
Color.fromRGBO(82, 116, 211, 0.933),
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
child: Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Elevated button for initiating firmware update.
ElevatedButton(
onPressed: () async {
// Assume the user will type the path manually.
customPath = await showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Enter Custom Firmware Path'),
content: TextField(
onChanged: (value) {
customPath = value;
},
decoration: const InputDecoration(
hintText: 'Enter Firmware Path',
),
),
actions: [
// OK button pressed after entering the custom path.
ElevatedButton(
onPressed: () {
Navigator.pop(context, customPath);
},
child: const Text('OK'),
),
],
),
);
// Check if user canceled or did not enter a path.
if (customPath == null || customPath!.isEmpty) {
// User canceled or did not enter a path.
firmwareFileSelected = false;
return;
} else {
// User provided a custom firmware path.
firmwareFileSelected = true;
}
// Get connected Bluetooth device and services from the BLE controller.
BluetoothDevice? device =
Get.find<BleDeviceController>().connectedDevice;
List<BluetoothService> services =
Get.find<BleDeviceController>().bleServices;
// Check if device and services are available for OTA update.
if (device == null || services.isEmpty) {
print("Device or services not available for OTA update");
return;
}
// Flag to check if required characteristics are found.
bool characteristicsFound = false;
// Iterate through the available services to find the required characteristics.
for (BluetoothService service in services) {
if (service.uuid.toString() ==
'd6f1d96d-594c-4c53-b1c6-144a1dfde6d8') {
final characteristics = service.characteristics;
BluetoothCharacteristic? dataUuid;
BluetoothCharacteristic? controlUuid;
// Iterate through characteristics to find data and control UUIDs.
for (BluetoothCharacteristic c in characteristics) {
if (c.uuid.toString() ==
'23408888-1f40-4cd8-9b89-ca8d45f8a5b0') {
dataUuid = c;
}
if (c.uuid.toString() ==
'7ad671aa-21c0-46a4-b722-270e3ae3d830') {
controlUuid = c;
}
}
// If both data and control UUIDs are found, proceed with OTA update.
if (dataUuid != null && controlUuid != null) {
// Adjust MTU size based on the platform (Android or iOS).
if (Platform.isAndroid) {
const newMtu = 300;
await device.requestMtu(newMtu);
print('New MTU size (Android): $newMtu');
} else if (Platform.isIOS) {
const newMtu = 185;
print('New MTU size (iOS): $newMtu');
}
// Create an OTA package instance.
final esp32otaPackage = Esp32OtaPackage(dataUuid, controlUuid);
// Show a progress dialog during the OTA update.
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => AlertDialog(
title: const Text('OTA Update in Progress'),
content: StreamBuilder<int>(
stream: esp32otaPackage.percentageStream,
initialData: 0,
builder: (BuildContext context,
AsyncSnapshot<int> snapshot) {
double progress =
snapshot.data! / 100.toDouble();
// Handle completion of OTA update and show a snackbar.
if (progress >= 1.0 && showProgressDialog) {
WidgetsBinding.instance
?.addPostFrameCallback((_) {
showProgressDialog = false;
Navigator.pop(context);
ScaffoldMessenger.of(context)
.showSnackBar(
const SnackBar(
content: Text('OTA Update Complete'),
duration: Duration(seconds: 2),
),
);
});
}
return LinearProgressIndicator(
value: progress,
valueColor:
const AlwaysStoppedAnimation<Color>(
Colors.blue),
backgroundColor: Colors.grey[300],
);
},
),
),
);
// Perform the firmware update if a firmware file is selected.
if (firmwareFileSelected) {
await esp32otaPackage.updateFirmware(
device,
2, // Set firmwareType to 2 for file picker
service,
dataUuid,
controlUuid,
);
}
// Set flag to indicate that characteristics were found.
characteristicsFound = true;
break;
}
}
}
// Show a dialog if the required characteristics are not found.
if (!characteristicsFound) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Device Not Compatible'),
content: const Text(
'The device does not have the required characteristics for OTA firmware update.',
),
actions: [
ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('OK'),
),
],
),
);
}
},
// Elevated button for initiating OTA update.
child: const Text('OTA Update'),
),
const SizedBox(height: 50),
const Text(
'Send Command',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 20),
// Text field for entering commands.
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20), // Add desired horizontal padding
child: TextField(
controller: commandController,
enabled: true,
keyboardType: TextInputType.text,
decoration: InputDecoration(
hintText: 'Enter Command Here',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
contentPadding: const EdgeInsets.all(16),
),
),
),
// Elevated button for sending commands via BLE.
ElevatedButton(
onPressed: () {
String command = commandController.text;
print("Sent data: $command");
// Send the command via BLE UART.
bleUartController.sendCommand(command);
// Hide the keyboard after sending the command.
FocusScope.of(context).unfocus();
},
child: const Text('Send'),
),
const SizedBox(height: 20),
],
),
),
),
),
);
}
}