Skip to content

Commit 4ffb1a9

Browse files
feat: Added next gen profile
1 parent 18a099e commit 4ffb1a9

File tree

8 files changed

+476
-121
lines changed

8 files changed

+476
-121
lines changed
Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
import 'package:badgemagic/bademagic_module/bluetooth/base_ble_state.dart';
2+
import 'package:badgemagic/bademagic_module/bluetooth/datagenerator.dart';
23

34
class CompletedState extends NormalBleState {
45
final bool isSuccess;
56
final String message;
7+
final TransferMode? mode;
8+
final bool shouldDisconnect;
69

7-
CompletedState({required this.isSuccess, required this.message});
10+
CompletedState({
11+
required this.isSuccess,
12+
required this.message,
13+
this.mode,
14+
this.shouldDisconnect = false,
15+
});
816

917
@override
1018
Future<BleState?> processState() async {
@@ -13,6 +21,7 @@ class CompletedState extends NormalBleState {
1321
} else {
1422
toast.showErrorToast(message);
1523
}
24+
1625
return null;
1726
}
1827
}
Lines changed: 21 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,39 @@
1+
import 'package:badgemagic/bademagic_module/bluetooth/base_ble_state.dart';
2+
import 'package:badgemagic/bademagic_module/bluetooth/completed_state.dart';
13
import 'package:badgemagic/bademagic_module/bluetooth/datagenerator.dart';
24
import 'package:badgemagic/bademagic_module/bluetooth/write_state.dart';
35
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
4-
import 'base_ble_state.dart';
56

6-
class ConnectState extends RetryBleState {
7+
class ConnectState extends NormalBleState {
78
final ScanResult scanResult;
89
final DataTransferManager manager;
910

10-
ConnectState({required this.manager, required this.scanResult});
11+
ConnectState({required this.scanResult, required this.manager});
1112

1213
@override
1314
Future<BleState?> processState() async {
14-
try {
15-
try {
16-
await scanResult.device.disconnect();
17-
logger.d("Pre-emptive disconnect for clean state");
18-
await Future.delayed(const Duration(seconds: 1));
19-
} catch (_) {
20-
logger.d("No existing connection to disconnect");
21-
}
22-
23-
await scanResult.device.connect(autoConnect: false);
24-
BluetoothConnectionState connectionState =
25-
await scanResult.device.connectionState.first;
15+
BluetoothDevice device = scanResult.device;
16+
manager.connectedDevice = device;
2617

27-
if (connectionState == BluetoothConnectionState.connected) {
28-
logger.d("Device connected successfully");
29-
toast.showToast('Device connected successfully.');
18+
try {
19+
toast.showToast("Connecting to ${device.platformName}...");
20+
logger.d("Attempting to connect to ${device.platformName}...");
3021

31-
manager.connectedDevice = scanResult.device;
22+
await device.connect(autoConnect: false);
23+
await Future.delayed(const Duration(milliseconds: 500));
3224

33-
final writeState = WriteState(
34-
device: scanResult.device,
35-
manager: manager,
36-
);
25+
logger.d("Connected to device: ${device.platformName}");
26+
toast.showToast("Connected to ${device.platformName}");
3727

38-
return await writeState.process();
39-
} else {
40-
throw Exception("Failed to connect to the device");
41-
}
28+
return WriteState(manager: manager, device: device);
4229
} catch (e) {
43-
toast.showErrorToast('Failed to connect. Retrying...');
44-
rethrow;
30+
logger.e("Connection failed: $e");
31+
return CompletedState(
32+
isSuccess: false,
33+
message: "Failed to connect to device. Please retry.",
34+
mode: manager.mode,
35+
shouldDisconnect: true, // ensure cleanup
36+
);
4537
}
4638
}
4739
}
Lines changed: 144 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,170 @@
1+
// Enhanced DataTransferManager with streaming support
2+
import 'dart:async';
13
import 'package:badgemagic/bademagic_module/models/data.dart';
24
import 'package:badgemagic/bademagic_module/utils/data_to_bytearray_converter.dart';
35
import 'package:badgemagic/bademagic_module/utils/file_helper.dart';
46
import 'package:badgemagic/providers/badge_message_provider.dart';
57
import 'package:badgemagic/providers/imageprovider.dart';
68
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
79
import 'package:get_it/get_it.dart';
10+
import 'package:logger/logger.dart';
11+
12+
enum TransferMode { legacy, streaming }
813

914
class DataTransferManager {
10-
final Data data;
15+
final Data? data; // Make nullable for streaming-only usage
16+
TransferMode mode;
1117

1218
BluetoothDevice? connectedDevice;
1319

20+
// Legacy characteristics
21+
BluetoothCharacteristic? legacyWriteCharacteristic;
22+
23+
// Streaming characteristics
24+
BluetoothCharacteristic? streamingWriteCharacteristic;
25+
BluetoothCharacteristic? streamingNotifyCharacteristic;
26+
StreamSubscription<List<int>>? notificationSubscription;
27+
1428
final BadgeMessageProvider badgeData = BadgeMessageProvider();
1529
final DataToByteArrayConverter converter = DataToByteArrayConverter();
1630
final FileHelper fileHelper = FileHelper();
1731
final InlineImageProvider controllerData =
1832
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);
1942

20-
DataTransferManager(this.data);
43+
factory DataTransferManager.forStreaming(Data data) =>
44+
DataTransferManager(null, mode: TransferMode.streaming);
2145

2246
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!);
2449
}
2550

26-
/// Helper to clear the currently connected device.
51+
/// Clear the currently connected device and cleanup
2752
void clearConnectedDevice() {
2853
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;
29169
}
30170
}

lib/bademagic_module/bluetooth/scan_state.dart

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,32 +14,39 @@ class ScanState extends NormalBleState {
1414
manager.clearConnectedDevice();
1515
await FlutterBluePlus.stopScan();
1616

17-
toast.showToast("Searching for device...");
17+
String searchMessage = manager.mode == TransferMode.streaming
18+
? "Searching for streaming device..."
19+
: "Searching for device...";
20+
toast.showToast(searchMessage);
21+
1822
Completer<BleState?> nextStateCompleter = Completer();
1923
StreamSubscription<List<ScanResult>>? subscription;
20-
2124
bool isCompleted = false;
25+
2226
try {
2327
subscription = FlutterBluePlus.scanResults.listen(
2428
(results) async {
2529
if (isCompleted || results.isEmpty) return;
2630

2731
try {
2832
final foundDevice = results.firstWhere(
29-
(r) => r.advertisementData.serviceUuids.contains(
30-
Guid("0000fee0-0000-1000-8000-00805f9b34fb"),
31-
),
32-
orElse: () => throw Exception("Matching device not found."),
33+
(r) => _deviceSupportsRequiredMode(r, manager.mode),
34+
orElse: () => throw Exception("Compatible device not found."),
3335
);
3436

3537
isCompleted = true;
3638
await FlutterBluePlus.stopScan();
37-
toast.showToast('Device found. Connecting...');
39+
40+
String foundMessage = manager.mode == TransferMode.streaming
41+
? 'Streaming device found. Connecting...'
42+
: 'Device found. Connecting...';
43+
toast.showToast(foundMessage);
44+
3845
nextStateCompleter.complete(
3946
ConnectState(scanResult: foundDevice, manager: manager),
4047
);
4148
} catch (_) {
42-
// Ignore and keep scanning
49+
// keep scanning until timeout
4350
}
4451
},
4552
onError: (e) {
@@ -55,22 +62,16 @@ class ScanState extends NormalBleState {
5562
},
5663
);
5764

65+
// Start scan with proper services
66+
List<Guid> services = _getServicesForMode(manager.mode);
5867
await FlutterBluePlus.startScan(
59-
withServices: [Guid("0000fee0-0000-1000-8000-00805f9b34fb")],
68+
withServices: services,
6069
removeIfGone: const Duration(seconds: 5),
6170
continuousUpdates: true,
6271
timeout: const Duration(seconds: 15),
6372
);
6473

65-
await Future.delayed(const Duration(seconds: 1));
66-
67-
if (!isCompleted) {
68-
isCompleted = true;
69-
FlutterBluePlus.stopScan();
70-
toast.showErrorToast('Device not found.');
71-
nextStateCompleter.completeError(Exception('Device not found.'));
72-
}
73-
74+
// Instead of 1s fixed delay, let the scan timeout handle completion
7475
return await nextStateCompleter.future;
7576
} catch (e) {
7677
logger.e("Exception during scanning: $e");
@@ -80,4 +81,39 @@ class ScanState extends NormalBleState {
8081
await FlutterBluePlus.stopScan();
8182
}
8283
}
84+
85+
bool _deviceSupportsRequiredMode(ScanResult result, TransferMode mode) {
86+
List<Guid> serviceUuids = result.advertisementData.serviceUuids;
87+
88+
switch (mode) {
89+
case TransferMode.legacy:
90+
return serviceUuids
91+
.contains(Guid("0000fee0-0000-1000-8000-00805f9b34fb"));
92+
case TransferMode.streaming:
93+
// Prefer next-gen service, fallback to legacy
94+
if (serviceUuids
95+
.contains(Guid("0000f055-0000-1000-8000-00805f9b34fb"))) {
96+
return true; // ✅ streaming
97+
}
98+
if (serviceUuids
99+
.contains(Guid("0000fee0-0000-1000-8000-00805f9b34fb"))) {
100+
logger.w("Streaming service not found, falling back to legacy.");
101+
manager.mode = TransferMode.legacy; // switch mode internally
102+
return true;
103+
}
104+
return false;
105+
}
106+
}
107+
108+
List<Guid> _getServicesForMode(TransferMode mode) {
109+
switch (mode) {
110+
case TransferMode.legacy:
111+
return [Guid("0000fee0-0000-1000-8000-00805f9b34fb")];
112+
case TransferMode.streaming:
113+
return [
114+
Guid("0000f055-0000-1000-8000-00805f9b34fb"), // next-gen
115+
Guid("0000fee0-0000-1000-8000-00805f9b34fb"), // legacy fallback
116+
];
117+
}
118+
}
83119
}

0 commit comments

Comments
 (0)