Skip to main content

gicisky_lib 0.1.1 copy "gicisky_lib: ^0.1.1" to clipboard
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),
                    ),
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}
0
likes
120
points
100
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A Flutter library for interacting with Gicisky BLE devices

Homepage

License

MIT (license)

Dependencies

flutter, flutter_blue_plus, image, logging, universal_ble

More

Packages that depend on gicisky_lib