|
| 1 | +// Enhanced DataTransferManager with streaming support |
| 2 | +import 'dart:async'; |
1 | 3 | import 'package:badgemagic/bademagic_module/models/data.dart';
|
2 | 4 | import 'package:badgemagic/bademagic_module/utils/data_to_bytearray_converter.dart';
|
3 | 5 | import 'package:badgemagic/bademagic_module/utils/file_helper.dart';
|
4 | 6 | import 'package:badgemagic/providers/badge_message_provider.dart';
|
5 | 7 | import 'package:badgemagic/providers/imageprovider.dart';
|
6 | 8 | import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
7 | 9 | import 'package:get_it/get_it.dart';
|
| 10 | +import 'package:logger/logger.dart'; |
| 11 | + |
| 12 | +enum TransferMode { legacy, streaming } |
8 | 13 |
|
9 | 14 | class DataTransferManager {
|
10 |
| - final Data data; |
| 15 | + final Data? data; // Make nullable for streaming-only usage |
| 16 | + TransferMode mode; |
11 | 17 |
|
12 | 18 | BluetoothDevice? connectedDevice;
|
13 | 19 |
|
| 20 | + // Legacy characteristics |
| 21 | + BluetoothCharacteristic? legacyWriteCharacteristic; |
| 22 | + |
| 23 | + // Streaming characteristics |
| 24 | + BluetoothCharacteristic? streamingWriteCharacteristic; |
| 25 | + BluetoothCharacteristic? streamingNotifyCharacteristic; |
| 26 | + StreamSubscription<List<int>>? notificationSubscription; |
| 27 | + |
14 | 28 | final BadgeMessageProvider badgeData = BadgeMessageProvider();
|
15 | 29 | final DataToByteArrayConverter converter = DataToByteArrayConverter();
|
16 | 30 | final FileHelper fileHelper = FileHelper();
|
17 | 31 | final InlineImageProvider controllerData =
|
18 | 32 | GetIt.instance<InlineImageProvider>();
|
| 33 | + final Logger logger = Logger(); |
| 34 | + |
| 35 | + bool isStreamingActive = false; |
| 36 | + |
| 37 | + DataTransferManager(this.data, {this.mode = TransferMode.legacy}); |
| 38 | + |
| 39 | + // Factory constructors for convenience |
| 40 | + factory DataTransferManager.forLegacy(Data data) => |
| 41 | + DataTransferManager(data, mode: TransferMode.legacy); |
19 | 42 |
|
20 |
| - DataTransferManager(this.data); |
| 43 | + factory DataTransferManager.forStreaming(Data data) => |
| 44 | + DataTransferManager(null, mode: TransferMode.streaming); |
21 | 45 |
|
22 | 46 | Future<List<List<int>>> generateDataChunk() async {
|
23 |
| - return converter.convert(data); |
| 47 | + if (data == null) throw Exception("No data provided for legacy transfer"); |
| 48 | + return converter.convert(data!); |
24 | 49 | }
|
25 | 50 |
|
26 |
| - /// Helper to clear the currently connected device. |
| 51 | + /// Clear the currently connected device and cleanup |
27 | 52 | void clearConnectedDevice() {
|
28 | 53 | connectedDevice = null;
|
| 54 | + legacyWriteCharacteristic = null; |
| 55 | + streamingWriteCharacteristic = null; |
| 56 | + streamingNotifyCharacteristic = null; |
| 57 | + notificationSubscription?.cancel(); |
| 58 | + notificationSubscription = null; |
| 59 | + isStreamingActive = false; |
| 60 | + } |
| 61 | + |
| 62 | + /// Handle error codes from streaming badge |
| 63 | + void handleStreamingErrorCode(int errorCode) { |
| 64 | + switch (errorCode) { |
| 65 | + case 0x00: |
| 66 | + logger.d("Streaming command executed successfully"); |
| 67 | + break; |
| 68 | + case 0xff: |
| 69 | + logger.e("Streaming parameters out of range"); |
| 70 | + break; |
| 71 | + case 0xfe: |
| 72 | + logger.e("Height larger than maximum allowed"); |
| 73 | + break; |
| 74 | + case 0xfd: |
| 75 | + logger.e("Message length not matched"); |
| 76 | + break; |
| 77 | + case 0xfc: |
| 78 | + logger.e("Missing pixel contents"); |
| 79 | + break; |
| 80 | + case 0x01: |
| 81 | + logger.e("Flash writing error"); |
| 82 | + break; |
| 83 | + case 0x02: |
| 84 | + logger.e("Speed/brightness out of range"); |
| 85 | + break; |
| 86 | + default: |
| 87 | + logger.e("Unknown streaming error code: $errorCode"); |
| 88 | + } |
| 89 | + } |
| 90 | + |
| 91 | + /// Stream bitmap data (for streaming mode) |
| 92 | + Future<bool> streamBitmap(List<int> bitmap) async { |
| 93 | + if (!isStreamingActive || streamingWriteCharacteristic == null) { |
| 94 | + logger.e("Streaming not active or characteristic unavailable"); |
| 95 | + return false; |
| 96 | + } |
| 97 | + |
| 98 | + try { |
| 99 | + List<int> command = [0x03]; // stream_bitmap function code |
| 100 | + |
| 101 | + // Convert bitmap to 16-bit words (little-endian) |
| 102 | + for (int column in bitmap) { |
| 103 | + command.add(column & 0xFF); |
| 104 | + command.add((column >> 8) & 0xFF); |
| 105 | + } |
| 106 | + |
| 107 | + await streamingWriteCharacteristic! |
| 108 | + .write(command, withoutResponse: false); |
| 109 | + logger.d("Bitmap streamed successfully, ${bitmap.length} columns"); |
| 110 | + return true; |
| 111 | + } catch (e) { |
| 112 | + logger.e("Failed to stream bitmap: $e"); |
| 113 | + return false; |
| 114 | + } |
| 115 | + } |
| 116 | + |
| 117 | + /// Enter streaming mode |
| 118 | + Future<bool> enterStreamingMode() async { |
| 119 | + if (streamingWriteCharacteristic == null) return false; |
| 120 | + |
| 121 | + try { |
| 122 | + List<int> command = [0x02, 0x00]; // Enter streaming mode |
| 123 | + await streamingWriteCharacteristic! |
| 124 | + .write(command, withoutResponse: false); |
| 125 | + await Future.delayed(const Duration(milliseconds: 100)); |
| 126 | + isStreamingActive = true; |
| 127 | + return true; |
| 128 | + } catch (e) { |
| 129 | + logger.e("Failed to enter streaming mode: $e"); |
| 130 | + return false; |
| 131 | + } |
| 132 | + } |
| 133 | + |
| 134 | + /// Exit streaming mode |
| 135 | + Future<bool> exitStreamingMode() async { |
| 136 | + if (streamingWriteCharacteristic == null) return false; |
| 137 | + |
| 138 | + try { |
| 139 | + List<int> command = [0x02, 0x01]; // Exit streaming mode |
| 140 | + await streamingWriteCharacteristic! |
| 141 | + .write(command, withoutResponse: false); |
| 142 | + isStreamingActive = false; |
| 143 | + return true; |
| 144 | + } catch (e) { |
| 145 | + logger.e("Failed to exit streaming mode: $e"); |
| 146 | + return false; |
| 147 | + } |
| 148 | + } |
| 149 | + |
| 150 | + /// Convert 2D bitmap to column format for streaming |
| 151 | + List<int> convertBitmapToColumns(List<List<bool>> bitmap) { |
| 152 | + if (bitmap.isEmpty) return []; |
| 153 | + |
| 154 | + int height = bitmap.length; |
| 155 | + int width = bitmap[0].length; |
| 156 | + List<int> columns = []; |
| 157 | + |
| 158 | + for (int col = 0; col < width; col++) { |
| 159 | + int columnValue = 0; |
| 160 | + for (int row = 0; row < height && row < 16; row++) { |
| 161 | + if (bitmap[row][col]) { |
| 162 | + columnValue |= (1 << row); |
| 163 | + } |
| 164 | + } |
| 165 | + columns.add(columnValue); |
| 166 | + } |
| 167 | + |
| 168 | + return columns; |
29 | 169 | }
|
30 | 170 | }
|
0 commit comments