Skip to content

Commit

Permalink
OTA speed improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
Codel1417 committed Sep 17, 2024
1 parent 81984f6 commit 823dcb9
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 82 deletions.
6 changes: 3 additions & 3 deletions .github/actions/build_android/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ runs:
ANDROID_KEY_JKS: ${{ inputs.android-key-jks }}
- name: Build APK
shell: bash
run: flutter build apk --build-number="$BUILD_NUMBER" --build-name="$VERSION" --dart-define=SENTRY_DSN="$SENTRY_DSN"
run: flutter build apk --build-number="$BUILD_NUMBER" --build-name="$VERSION" --dart-define=SENTRY_DSN="$SENTRY_DSN" --dart-define=cronetHttpNoPlay=true
env:
VERSION: ${{ inputs.version }}
BUILD_NUMBER: ${{ inputs.build-number }}
Expand All @@ -56,7 +56,7 @@ runs:
#SENTRY_URL: ${{ inputs.sentry-url }}
- name: Build AppBundle
shell: bash
run: flutter build appbundle --build-number="$BUILD_NUMBER" --build-name="$VERSION" --dart-define=SENTRY_DSN="$SENTRY_DSN"
run: flutter build appbundle --build-number="$BUILD_NUMBER" --build-name="$VERSION" --dart-define=SENTRY_DSN="$SENTRY_DSN" --dart-define=cronetHttpNoPlay=true
env:
VERSION: ${{ inputs.version }}
BUILD_NUMBER: ${{ inputs.build-number }}
Expand All @@ -66,7 +66,7 @@ runs:
SENTRY_DSN: ${{ inputs.sentry-dsn }}

#SENTRY_URL: ${{ inputs.sentry-url }}
- name: list dependencies
- name: list dependencies
shell: bash
run: cd android && ./gradlew app:dependencies --configuration releaseCompileClasspath
- uses: actions/upload-artifact@v4
Expand Down
1 change: 1 addition & 0 deletions .run/main.dart.run.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="main.dart" type="FlutterRunConfigurationType" factoryName="Flutter">
<option name="additionalArgs" value="--dart-define=cronetHttpNoPlay=true" />
<option name="filePath" value="$PROJECT_DIR$/lib/main.dart" />
<method v="2">
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Get dart dependencies" run_configuration_type="ShConfigurationType" />
Expand Down
172 changes: 93 additions & 79 deletions lib/Backend/firmware_update.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import 'dart:async';
import 'dart:convert';
import 'dart:isolate';

import 'package:collection/collection.dart';
import 'package:crypto/crypto.dart';
import 'package:dio/dio.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:logging/logging.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:tail_app/Backend/plausible_dio.dart';
import 'package:wakelock_plus/wakelock_plus.dart';

Expand Down Expand Up @@ -130,7 +132,6 @@ class OtaUpdater {
String? downloadedMD5;
bool _wakelockEnabledBeforehand = false;
int current = 0;
Duration timeRemainingMS = Duration.zero;
Timer? _timer;
final Logger _otaLogger = Logger('otaLogger');

Expand All @@ -145,9 +146,15 @@ class OtaUpdater {
downloadProgress = 1;
}

double _previousProgress = 0;

void _updateProgress() {
if ((_previousProgress - _progress).abs() < 0.1) {
return;
}
if (onProgress != null) {
onProgress!((downloadProgress + uploadProgress) / 2);
onProgress!(_progress);
_previousProgress = _progress;
}
}

Expand Down Expand Up @@ -179,7 +186,6 @@ class OtaUpdater {
otaState = OtaState.download;
downloadProgress = 0;
_updateProgress();
final transaction = Sentry.startTransaction('OTA Download', 'http')..setTag("GearType", baseStatefulDevice.baseDeviceDefinition.btName);
try {
final Response<List<int>> rs = await (await initDio()).get<List<int>>(
firmwareInfo!.url,
Expand All @@ -196,17 +202,12 @@ class OtaUpdater {
if (digest.toString() == firmwareInfo!.md5sum) {
firmwareFile = rs.data;
} else {
transaction.status = const SpanStatus.dataLoss();
otaState = OtaState.error;
}
}
} catch (e) {
transaction
..throwable = e
..status = const SpanStatus.internalError();
otaState = OtaState.error;
}
transaction.finish();
}

Future<void> verListener() async {
Expand All @@ -225,81 +226,61 @@ class OtaUpdater {
Future<void> uploadFirmware() async {
otaState = OtaState.upload;
uploadProgress = 0;
Stopwatch timeToUpdate = Stopwatch();
final transaction = Sentry.startTransaction('updateGear()', 'task');
try {
if (firmwareFile != null) {
transaction.setTag("GearType", baseStatefulDevice.baseDeviceDefinition.btName);
baseStatefulDevice.gearReturnedError.value = false;
int mtu = baseStatefulDevice.mtu.value - 10;
int total = firmwareFile!.length;
current = 0;
baseStatefulDevice.gearReturnedError.value = false;

_otaLogger.info("Holding the command queue");
timeToUpdate.start();
_otaLogger.info("Send OTA begin message");
List<int> beginOTA = List.from(const Utf8Encoder().convert("OTA ${firmwareFile!.length} $downloadedMD5"));
await sendMessage(baseStatefulDevice, beginOTA);

while (uploadProgress < 1 && otaState != OtaState.error) {
baseStatefulDevice.deviceState.value = DeviceState.busy; // hold the command queue
if (baseStatefulDevice.gearReturnedError.value) {
transaction.status = const SpanStatus.unavailable();
otaState = OtaState.error;

break;
}
if (firmwareFile != null) {
int mtu = baseStatefulDevice.mtu.value - 10;
current = 0;
baseStatefulDevice.gearReturnedError.value = false;

_otaLogger.info("Holding the command queue");
_otaLogger.info("Send OTA begin message");
List<int> beginOTA = List.from(const Utf8Encoder().convert("OTA ${firmwareFile!.length} $downloadedMD5"));
await sendMessage(baseStatefulDevice, beginOTA);

while (uploadProgress < 1 && otaState != OtaState.error) {
baseStatefulDevice.deviceState.value = DeviceState.busy; // hold the command queue
if (baseStatefulDevice.gearReturnedError.value) {
otaState = OtaState.error;
break;
}

List<int> chunk = firmwareFile!.skip(current).take(mtu).toList();
if (chunk.isNotEmpty) {
try {
//_otaLogger.info("Updating $uploadProgress");
if (current > 0) {
timeRemainingMS = Duration(milliseconds: ((timeToUpdate.elapsedMilliseconds / current) * (total - current)).toInt());
}

await sendMessage(baseStatefulDevice, chunk, withoutResponse: true);
} catch (e, s) {
_otaLogger.severe("Exception during ota upload:$e", e, s);
if ((current + chunk.length) / total < 0.99) {
transaction
..status = const SpanStatus.unknownError()
..throwable = e;
otaState = OtaState.error;
return;
}
List<int> chunk = firmwareFile!.skip(current).take(mtu).toList();
if (chunk.isNotEmpty) {
try {
await sendMessage(baseStatefulDevice, chunk, withoutResponse: true);
} catch (e, s) {
_otaLogger.severe("Exception during ota upload:$e", e, s);
if ((current + chunk.length) / firmwareFile!.length < 0.99) {
otaState = OtaState.error;
return;
}
current = current + chunk.length;
} else {
current = total;
}

uploadProgress = current / total;
_updateProgress();
}
if (uploadProgress == 1) {
_otaLogger.info("File Uploaded");
otaState = OtaState.rebooting;
beginScan(
scanReason: ScanReason.manual,
timeout: const Duration(seconds: 60),
); // start scanning for the gear to reconnect
_timer = Timer(
const Duration(seconds: 60),
() {
if (otaState != OtaState.completed) {
_otaLogger.warning("Gear did not return correct version after reboot");
otaState = OtaState.error;
}
},
);
plausible.event(name: "Update Gear");
current = current + chunk.length;
} else {
current = firmwareFile!.length;
}
baseStatefulDevice.deviceState.value = DeviceState.standby; // release the command queue

uploadProgress = current / firmwareFile!.length;
_updateProgress();
}
} finally {
transaction.finish();
if (uploadProgress == 1) {
_otaLogger.info("File Uploaded");
otaState = OtaState.rebooting;
beginScan(
scanReason: ScanReason.manual,
timeout: const Duration(seconds: 60),
); // start scanning for the gear to reconnect
_timer = Timer(
const Duration(seconds: 60),
() {
if (otaState != OtaState.completed) {
_otaLogger.warning("Gear did not return correct version after reboot");
otaState = OtaState.error;
}
},
);
plausible.event(name: "Update Gear");
}
baseStatefulDevice.deviceState.value = DeviceState.standby; // release the command queue
}
}

Expand All @@ -320,3 +301,36 @@ class OtaUpdater {
}
}
}

@pragma('vm:entry-point')
Future<void> updaterIsolate({required String macAddress, required String service, required String txCharacteristic, required List<int> firmwareFile, required String md5Hash, required SendPort port}) async {
BluetoothDevice? bluetoothDevice = FlutterBluePlus.connectedDevices.where((element) => element.remoteId.str == macAddress).firstOrNull;
if (bluetoothDevice == null) {
return;
}
BluetoothCharacteristic? bluetoothCharacteristic = bluetoothDevice.servicesList.firstWhereOrNull((element) => element.uuid == Guid(service))?.characteristics.firstWhereOrNull((element) => element.characteristicUuid == Guid(txCharacteristic));
if (bluetoothCharacteristic == null) {
return;
}
List<int> beginOTA = List.from(const Utf8Encoder().convert("OTA ${firmwareFile.length} $md5Hash"));
await bluetoothCharacteristic.write(beginOTA);
int current = 0;
double uploadProgress = 0;

List<int> chunk = firmwareFile.skip(current).take(bluetoothDevice.mtuNow).toList();
if (chunk.isNotEmpty) {
try {
await bluetoothCharacteristic.write(chunk);
} catch (e, s) {
if ((current + chunk.length) / firmwareFile.length < 0.99) {
port.send("Error");
return;
}
}
current = current + chunk.length;
} else {
current = firmwareFile.length;
}
uploadProgress = current / firmwareFile.length;
port.send(uploadProgress);
}
2 changes: 2 additions & 0 deletions lib/Frontend/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:logarte/logarte.dart';
import 'package:logging/logging.dart';
import 'package:native_dio_adapter/native_dio_adapter.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:platform/platform.dart';
import 'package:sentry_dio/sentry_dio.dart';
Expand Down Expand Up @@ -80,6 +81,7 @@ Future<Dio> initDio({skipSentry = false}) async {
],
),
);
dio.httpClientAdapter = NativeAdapter();
if (!skipSentry) {
/// This *must* be the last initialization step of the Dio setup, otherwise
/// your configuration of Dio might overwrite the Sentry configuration.
Expand Down
48 changes: 48 additions & 0 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.9.2"
cronet_http:
dependency: transitive
description:
name: cronet_http
sha256: "3af9c4d57bf07ef4b307e77b22be4ad61bea19ee6ff65e62184863f3a09f1415"
url: "https://pub.dev"
source: hosted
version: "1.3.2"
cross_file:
dependency: transitive
description:
Expand Down Expand Up @@ -360,6 +368,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.0"
cupertino_http:
dependency: transitive
description:
name: cupertino_http
sha256: "7e75c45a27cc13a886ab0a1e4d8570078397057bd612de9d24fe5df0d9387717"
url: "https://pub.dev"
source: hosted
version: "1.5.1"
custom_lint:
dependency: transitive
description:
Expand Down Expand Up @@ -615,6 +631,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "5.7.0"
flutter_isolate:
dependency: "direct main"
description:
name: flutter_isolate
sha256: "12f4c82b8bdf94e1bcde0bffc6d092d25ed73cc56b22f4cc8089f8b7fd74cf1f"
url: "https://pub.dev"
source: hosted
version: "2.1.0-pre"
flutter_joystick:
dependency: "direct main"
description:
Expand Down Expand Up @@ -878,6 +902,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.0.2"
http_profile:
dependency: transitive
description:
name: http_profile
sha256: "7e679e355b09aaee2ab5010915c932cce3f2d1c11c3b2dc177891687014ffa78"
url: "https://pub.dev"
source: hosted
version: "0.1.0"
image:
dependency: transitive
description:
Expand Down Expand Up @@ -951,6 +983,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.4"
jni:
dependency: transitive
description:
name: jni
sha256: f377c585ea9c08d48b427dc2e03780af2889d1bb094440da853c6883c1acba4b
url: "https://pub.dev"
source: hosted
version: "0.10.1"
js:
dependency: transitive
description:
Expand Down Expand Up @@ -1096,6 +1136,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.0.2"
native_dio_adapter:
dependency: "direct main"
description:
name: native_dio_adapter
sha256: "4c925ba15a44478be0eb6e97b62a1c1d07e56b28e566283dbcb15e58418bdaae"
url: "https://pub.dev"
source: hosted
version: "1.3.0"
nm:
dependency: transitive
description:
Expand Down
2 changes: 2 additions & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ dependencies:
firebase_testlab_detector: ^1.0.2
platform: ^3.1.5
connectivity_plus: ^6.0.5
flutter_isolate: ^2.0.4

# Riverpod
flutter_riverpod: ^2.5.1
Expand Down Expand Up @@ -82,6 +83,7 @@ dependencies:
# Dio HTTP
dio: ^5.7.0
dio_smart_retry: ^6.0.0
native_dio_adapter: ^1.3.0

# Sensors
pedometer: # Needs gradle namespace
Expand Down

0 comments on commit 823dcb9

Please sign in to comment.