gicisky_lib 0.1.1
gicisky_lib: ^0.1.1 copied to clipboard
A Flutter library for interacting with Gicisky BLE devices
example/lib/main.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:image/image.dart';
import 'package:logging/logging.dart';
import 'package:gicisky_lib/gicisky_lib.dart';
void main() {
// Initialize logging to print to console
Logger.root.level = Level.ALL; // Set the minimum log level
Logger.root.onRecord.listen((record) {
// Format log messages with timestamp, level, logger name, and message
final timestamp = record.time.toIso8601String().split('T')[1].substring(0, 12);
final level = record.level.name.padRight(7);
final logger = record.loggerName.padRight(20);
print('$timestamp $level $logger: ${record.message}');
if (record.error != null) {
print(' Error: ${record.error}');
}
if (record.stackTrace != null) {
print(' Stack trace:\n${record.stackTrace}');
}
});
runApp(const GiciskyExampleApp());
}
class GiciskyExampleApp extends StatelessWidget {
const GiciskyExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Gicisky Example',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final BluePlusBackend _ble = BluePlusBackend();
StreamSubscription<UploadProgress>? _progressSubscription;
Map<MacAddress, Advertisement>? _devices;
MacAddress? _selectedDeviceId;
DeviceData? _selectedDeviceData;
bool _isScanning = false;
bool _isUploading = false;
String _status = 'Ready';
double _uploadProgress = 0.0;
@override
void dispose() {
_progressSubscription?.cancel();
super.dispose();
}
/// Scan for Gicisky devices
Future<void> _scanDevices() async {
setState(() {
_isScanning = true;
_status = 'Scanning for devices...';
_devices = null;
_selectedDeviceId = null;
_selectedDeviceData = null;
});
try {
final devices = await _ble.scanDevices();
setState(() {
_devices = devices;
_isScanning = false;
_status = 'Found ${devices.length} compatible device(s)';
});
if (devices.isEmpty) {
_showError('No compatible devices found');
} else if (devices.length == 1) {
// Automatically select only device
final deviceId = devices.keys.first;
_selectDevice(deviceId);
}
} catch (e) {
setState(() {
_isScanning = false;
_status = 'Scan failed: $e';
});
_showError('Failed to scan: $e');
}
}
/// Select a device from the scanned list
void _selectDevice(MacAddress deviceId) {
final device = _devices![deviceId]!;
try {
final deviceData = parseAdvertisement(device);
setState(() {
_selectedDeviceId = deviceId;
_selectedDeviceData = deviceData;
_status = 'Selected: ${deviceData.name} (Model: 0x${deviceData.model.toRadixString(16).padLeft(2, '0').toUpperCase()})';
});
} catch (e) {
setState(() {
_selectedDeviceId = deviceId;
_selectedDeviceData = null;
_status = 'Selected: $deviceId (Failed to parse device data)';
});
_showError('Failed to parse device data: $e');
}
}
/// Load and convert image from assets
Future<GiciskyImageData> _loadImage(String assetPath) async {
try {
// Load test pattern from assets
final byteData = await rootBundle.load(assetPath);
// Decode image
final image = decodeImage(byteData.buffer.asUint8List());
if (image == null) {
throw Exception('Failed to decode image');
}
// Get the model ID
if (_selectedDeviceData == null) {
throw Exception('Unknown device model');
}
final modelId = _selectedDeviceData!.model;
// Optimize image for the device
final optimizedImage = optimize(image, modelId);
// Convert to Gicisky byte format
final giciskyBytes = convertToGiciskyBytes(optimizedImage, modelId);
return giciskyBytes;
} catch (e) {
throw Exception('Failed to load image: $e');
}
}
/// Upload image to the selected device
Future<void> _uploadImage(String assetPath) async {
if (_selectedDeviceId == null) {
_showError('Please select a device first');
return;
}
setState(() {
_isUploading = true;
_uploadProgress = 0.0;
_status = 'Loading image...';
});
try {
// Load image
final imageData = await _loadImage(assetPath);
setState(() {
_status = 'Connecting to device...';
});
// Connect to device
await _ble.connect(_selectedDeviceId!);
setState(() {
_status = 'Connected. Starting upload...';
});
// Create protocol instance
final protocol = GiciskyProtocol(ble: _ble);
// Subscribe to progress updates
_progressSubscription = protocol.progressStream.listen((progress) {
if (mounted) {
setState(() {
_uploadProgress = progress.progress;
_status = progress.message ?? 'Uploading...';
});
}
});
// Upload image
await protocol.uploadImage(imageData);
setState(() {
_status = 'Upload complete!';
_uploadProgress = 1.0;
});
// Refresh display (optional - not needed if disconnecting)
// await protocol.refreshDisplay(_selectedDeviceId!);
// Disconnect
setState(() {
_status = 'Disconnecting...';
});
await _ble.disconnect();
setState(() {
_status = 'Done!';
_isUploading = false;
});
_showSuccess('Image uploaded successfully!');
} catch (e) {
setState(() {
_status = 'Upload failed: $e';
_isUploading = false;
});
// Try to disconnect on error
try {
await _ble.disconnect();
} catch (_) {}
_showError('Upload failed: $e');
} finally {
_progressSubscription?.cancel();
}
}
void _showError(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: Colors.red,
),
);
}
void _showSuccess(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: Colors.green,
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('Gicisky Example'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Status section
Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Status',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(_status),
if (_isUploading) ...[
const SizedBox(height: 16),
LinearProgressIndicator(value: _uploadProgress),
],
],
),
),
),
const SizedBox(height: 16),
// Scan section
ElevatedButton.icon(
onPressed: _isScanning || _isUploading ? null : _scanDevices,
icon: _isScanning
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.bluetooth_searching),
label: Text(_isScanning ? 'Scanning...' : 'Scan for Devices'),
),
const SizedBox(height: 16),
// Device list
if (_devices != null && _devices!.isNotEmpty)
Expanded(
child: Card(
child: ListView.builder(
itemCount: _devices!.length,
itemBuilder: (context, index) {
final deviceId = _devices!.keys.elementAt(index);
final device = _devices![deviceId]!;
final isSelected = deviceId == _selectedDeviceId;
return ListTile(
title: Text(device.name),
subtitle: Text(
'ID: ${deviceId.toStringValue}',
),
trailing: isSelected
? const Icon(Icons.check_circle, color: Colors.green)
: null,
selected: isSelected,
onTap: () => _selectDevice(deviceId),
);
},
),
),
),
const SizedBox(height: 16),
// Device info section
if (_selectedDeviceData != null)
Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Device Information',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
Text('Model: 0x${_selectedDeviceData!.model.toRadixString(16).padLeft(2, '0').toUpperCase()}'),
Text('Firmware: ${_selectedDeviceData!.firmware}'),
Text('Hardware: ${_selectedDeviceData!.hardware}'),
Text('Battery: ${_selectedDeviceData!.battery}%'),
Text('Voltage: ${_selectedDeviceData!.voltage}V'),
],
),
),
),
if (_selectedDeviceData != null)
const SizedBox(height: 16),
// Upload buttons
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: _isUploading || _selectedDeviceId == null
? null
: () => _uploadImage('assets/test_pattern_1.png'),
icon: _isUploading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.cloud_upload),
label: Text(_isUploading ? 'Uploading...' : 'Upload Pattern 1'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
),
),
),
const SizedBox(width: 16),
Expanded(
child: ElevatedButton.icon(
onPressed: _isUploading || _selectedDeviceId == null
? null
: () => _uploadImage('assets/test_pattern_2.png'),
icon: _isUploading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.cloud_upload),
label: Text(_isUploading ? 'Uploading...' : 'Upload Pattern 2'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
),
),
),
],
),
],
),
),
);
}
}