diff --git a/.github/workflows/build_web_app.yml b/.github/workflows/build_web_app.yml new file mode 100644 index 0000000..8626cc2 --- /dev/null +++ b/.github/workflows/build_web_app.yml @@ -0,0 +1,37 @@ + +name: Build web app + +on: + push: + branches: [ "main" ] + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: '3.16.0' + channel: 'stable' + - run: flutter --version + + - name: Cache pub dependencies + uses: actions/cache@v2 + with: + path: ${{ env.FLUTTER_HOME }}/.pub-cache + key: ${{ runner.os }}-pub-${{ hashFiles('**/pubspec.lock') }} + restore-keys: ${{ runner.os }}-pub- + + - name: Download pub dependencies + run: flutter pub get + + - name: Build Web App + run: cd example && flutter build web --base-href=/universal_ble/ + + - name: Deploy to GitHub Pages 🚀 + uses: JamesIves/github-pages-deploy-action@v4 + with: + folder: example/build/web diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d0ef55..c236e4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ +## 0.8.4 +* Add `WebRequestOptionsBuilder.defaultServices` for convenience when setting `optionalServices` when scanning on web +* Add "try online" URL in readme +* Improve readme +* Improve example app + ## 0.8.3 -* Rename onPairStateChange -> onPairingStateChange +* Rename onPairStateChange *> onPairingStateChange * Improve readme ## 0.8.2 diff --git a/README.md b/README.md index 2a8840d..1fede3e 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,9 @@ [![universal_ble version](https://img.shields.io/pub/v/universal_ble?label=universal_ble)](https://pub.dev/packages/universal_ble) -A cross-platform (Android/iOS/macOS/Windows/Linux/Web) Bluetooth Low Energy (BLE) plugin for Flutter +A cross-platform (Android/iOS/macOS/Windows/Linux/Web) Bluetooth Low Energy (BLE) plugin for Flutter. + +[Try it online](https://navideck.github.io/universal_ble/), provided your browser supports [Web Bluetooth](https://caniuse.com/web-bluetooth). ## Features @@ -16,29 +18,31 @@ A cross-platform (Android/iOS/macOS/Windows/Linux/Web) Bluetooth Low Energy (BLE ### API Support Matrix | API | Android | iOS | macOS | Windows (beta) | Linux (beta) | Web | -| :------------------- | :-----: | :-: | :---: | :-----: | :---: | :-: | -| startScan/stopScan | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | -| connect/disconnect | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | -| getConnectedDevices | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | -| discoverServices | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | -| readValue | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | -| writeValue | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | -| setNotifiable | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | -| pair/unPair | ✔️ | ❌ | ❌ | ✔️ | ✔️ | ❌ | -| onPairingStateChange | ✔️ | ❌ | ❌ | ✔️ | ✔️ | ❌ | -| enableBluetooth | ✔️ | ❌ | ❌ | ✔️ | ✔️ | ❌ | -| onAvailabilityChange | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | -| requestMtu | ✔️ | ✔️ | ✔️ | ✔️ | 🚧 | ❌ | +| :------------------- | :-----: | :-: | :---: | :------------: | :----------: | :-: | +| startScan/stopScan | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | +| connect/disconnect | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | +| getConnectedDevices | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | +| discoverServices | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | +| readValue | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | +| writeValue | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | +| setNotifiable | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | +| pair/unPair | ✔️ | ❌ | ❌ | ✔️ | ✔️ | ❌ | +| onPairingStateChange | ✔️ | ❌ | ❌ | ✔️ | ✔️ | ❌ | +| enableBluetooth | ✔️ | ❌ | ❌ | ✔️ | ✔️ | ❌ | +| onAvailabilityChange | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | +| requestMtu | ✔️ | ✔️ | ✔️ | ✔️ | 🚧 | ❌ | ## Getting Started Add universal_ble in your pubspec.yaml: + ```yaml dependencies: universal_ble: ``` and import it wherever you want to use it: + ```dart import 'package:universal_ble/universal_ble.dart'; ``` @@ -188,4 +192,7 @@ UniversalBle.startScan( // Create a class that extends UniversalBlePlatform class UniversalBleMock extends UniversalBlePlatform { // Implement all methods -} \ No newline at end of file +} + +UniversalBle.setInstance(UniversalBleMock()); +``` diff --git a/example/lib/home/home.dart b/example/lib/home/home.dart index 2c92a3f..beaa5b8 100644 --- a/example/lib/home/home.dart +++ b/example/lib/home/home.dart @@ -1,5 +1,6 @@ // ignore_for_file: use_build_context_synchronously +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:universal_ble/universal_ble.dart'; import 'package:universal_ble_example/data/capabilities.dart'; @@ -35,6 +36,10 @@ class _MyAppState extends State { void initState() { super.initState(); + /// Add common services for web + if (kIsWeb) { + _services.addAll(WebRequestOptionsBuilder.defaultServices); + } _requestOptions = WebRequestOptionsBuilder.acceptAllDevices(optionalServices: _services); @@ -90,8 +95,17 @@ class _MyAppState extends State { _scanResults.clear(); _isScanning = true; }); - await UniversalBle.startScan( - webRequestOptions: _requestOptions); + try { + await UniversalBle.startScan( + webRequestOptions: _requestOptions); + } catch (e) { + setState(() { + _isScanning = false; + }); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(e.toString())), + ); + } }, ), PlatformButton( @@ -103,7 +117,8 @@ class _MyAppState extends State { }); }, ), - if (Capabilities.supportsBluetoothEnableApi) + if (Capabilities.supportsBluetoothEnableApi && + bleAvailabilityState == AvailabilityState.poweredOff) PlatformButton( text: 'Enable Bluetooth', onPressed: () async { diff --git a/example/lib/peripheral_details/peripheral_detail_page.dart b/example/lib/peripheral_details/peripheral_detail_page.dart index 5861b34..117ddb0 100644 --- a/example/lib/peripheral_details/peripheral_detail_page.dart +++ b/example/lib/peripheral_details/peripheral_detail_page.dart @@ -90,24 +90,6 @@ class _PeripheralDetailPageState extends State { } } - Uint8List? _getWriteValue() { - if (!valueFormKey.currentState!.validate()) return null; - if (binaryCode.text.isEmpty) { - print("Error: No value to write"); - return null; - } - - List hexList = []; - - try { - hexList = hex.decode(binaryCode.text); - } catch (e) { - print("Error parsing hex $e"); - } - - return Uint8List.fromList(hexList); - } - Future _discoverServices() async { var services = await UniversalBle.discoverServices(widget.deviceId); print('${services.length} services discovered'); @@ -115,6 +97,11 @@ class _PeripheralDetailPageState extends State { setState(() { discoveredServices = services; }); + + if (kIsWeb) { + _addLog("DiscoverServices", + '${services.length} services discovered,\nNote: Only services added in WebRequestOptionsBuilder will be discoverd'); + } } Future _readValue() async { @@ -134,9 +121,20 @@ class _PeripheralDetailPageState extends State { } Future _writeValue() async { - Uint8List? value = _getWriteValue(); - if (value == null || selectedCharacteristic == null) return; - print("Writing $value"); + if (selectedCharacteristic == null || + !valueFormKey.currentState!.validate() || + binaryCode.text.isEmpty) { + return; + } + + Uint8List value; + try { + value = Uint8List.fromList(hex.decode(binaryCode.text)); + } catch (e) { + _addLog('WriteError', "Error parsing hex $e"); + return; + } + try { await UniversalBle.writeValue( widget.deviceId, @@ -314,7 +312,12 @@ class _PeripheralDetailPageState extends State { if (value == null || value.isEmpty) { return 'Please enter a value'; } - return null; + try { + hex.decode(binaryCode.text); + return null; + } catch (e) { + return 'Please enter a valid hex value ( without spaces or 0x (e.g. F0BB) )'; + } }, decoration: const InputDecoration( hintText: @@ -337,15 +340,16 @@ class _PeripheralDetailPageState extends State { enabled: isConnected, text: 'Discover Services', ), - PlatformButton( - enabled: isConnected, - onPressed: () async { - int mtu = await UniversalBle.requestMtu( - widget.deviceId, 247); - _addLog('MTU', mtu); - }, - text: 'Request Mtu', - ), + if (Capabilities.supportsRequestMtuApi) + PlatformButton( + enabled: isConnected, + onPressed: () async { + int mtu = await UniversalBle.requestMtu( + widget.deviceId, 247); + _addLog('MTU', mtu); + }, + text: 'Request Mtu', + ), PlatformButton( enabled: isConnected && discoveredServices.isNotEmpty && diff --git a/lib/src/models/web_request_options_builder.dart b/lib/src/models/web_request_options_builder.dart index f017e7c..ff02505 100644 --- a/lib/src/models/web_request_options_builder.dart +++ b/lib/src/models/web_request_options_builder.dart @@ -47,6 +47,20 @@ class WebRequestOptionsBuilder { ); } } + + /// List of common services + static List get defaultServices { + return BluetoothDefaultServiceUUIDS.values + .map((e) => e.uuid.toString()) + .toList(); + } + + /// List of common services in 16 bit + static List get defaultServices16Bit { + return BluetoothDefaultServiceUUIDS.values + .map((e) => e.uuid16.toString()) + .toList(); + } } /// To filter by name, namePrefix, services, manufacturerData diff --git a/pubspec.yaml b/pubspec.yaml index 4bf416b..b934e8f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,9 +1,10 @@ name: universal_ble description: A cross-platform (Android/iOS/macOS/Windows/Linux/Web) Bluetooth Low Energy (BLE) plugin for Flutter -version: 0.8.3 +version: 0.8.4 homepage: https://navideck.com repository: https://github.com/Navideck/universal_ble issue_tracker: https://github.com/Navideck/universal_ble/issues + platforms: android: ios: @@ -11,7 +12,7 @@ platforms: macos: web: windows: - + environment: sdk: ">=3.1.3 <4.0.0" flutter: ">=3.3.0" @@ -30,6 +31,7 @@ dev_dependencies: sdk: flutter flutter_lints: ^2.0.0 pigeon: ^13.0.0 + flutter: plugin: platforms: @@ -44,3 +46,5 @@ flutter: sharedDarwinSource: true windows: pluginClass: UniversalBlePluginCApi + +