ESP32 | FLUTTER | BLE - Temperature & Humidity Check App

This commit is contained in:
Eric
2019-07-11 22:48:18 -07:00
parent 01da4d1b92
commit c3ef48e4f5
162 changed files with 12993 additions and 0 deletions

View File

@@ -0,0 +1,72 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.packages
.pub-cache/
.pub/
/build/
# Android related
**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages

View File

@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: b712a172f9694745f50505c93340883493b505e5
channel: stable
project_type: app

View File

@@ -0,0 +1,16 @@
# flutter_ble_app
A new Flutter application.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
For help getting started with Flutter, view our
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

View File

@@ -0,0 +1,61 @@
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 28
lintOptions {
disable 'InvalidPackage'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.eric.flutter_ble_app"
minSdkVersion 24
targetSdkVersion 28
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.eric.flutter_ble_app">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@@ -0,0 +1,33 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.eric.flutter_ble_app">
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<application
android:name="io.flutter.app.FlutterApplication"
android:label="flutter_ble_app"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- This keeps the window background of the activity showing
until Flutter renders its first frame. It can be removed if
there is no splash screen (such as the default splash screen
defined in @style/LaunchTheme). -->
<meta-data
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
android:value="true" />
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,13 @@
package com.eric.flutter_ble_app;
import android.os.Bundle;
import io.flutter.app.FlutterActivity;
import io.flutter.plugins.GeneratedPluginRegistrant;
public class MainActivity extends FlutterActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
}
}

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
</resources>

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.eric.flutter_ble_app">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@@ -0,0 +1,29 @@
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
}
}
allprojects {
repositories {
google()
jcenter()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}

View File

@@ -0,0 +1,2 @@
org.gradle.jvmargs=-Xmx1536M

View File

@@ -0,0 +1,6 @@
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip

View File

@@ -0,0 +1,15 @@
include ':app'
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
}
plugins.each { name, path ->
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
include ":$name"
project(":$name").projectDir = pluginDirectory
}

View File

@@ -0,0 +1,7 @@
.DS_Store
.dart_tool/
.packages
.pub/
build/

View File

@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 7a4c33425ddd78c54aba07d86f3f9a4a0051769b
channel: stable
project_type: plugin

View File

@@ -0,0 +1,97 @@
## 0.5.1
* Forked repo to internal use
* Upgrade Android protobuf dependencies
* Refresh iOS build files
## 0.5.0
* **Breaking change**. Migrate from the deprecated original Android Support
Library to AndroidX. This shouldn't result in any functional changes, but it
requires any Android apps using this plugin to [also
migrate](https://developer.android.com/jetpack/androidx/migrate) if they're
using the original support library.
## 0.4.2+1
* Upgrade Android Gradle plugin to 3.3.0
* Refresh iOS build files
## 0.4.2
* Set the verbosity of log messages with `setLogLevel`
* Updated iOS and Android project files
* `autoConnect` now configurable for Android
* Various bug fixes
## 0.4.1
* Fixed bug where setNotifyValue wasn't properly awaitable.
* Various UI bug fixes to example app.
* Removed unnecessary intl dependencies in example app.
## 0.4.0
* **Breaking change**. Manufacturer Data is now a `Map` of manufacturer ID's.
* Service UUID's, service data, tx power level packets fixed in advertising data.
* Example app updated to show advertising data.
* Various other bug fixes.
## 0.3.4
* Updated to use the latest protobuf (^0.9.0+1)
* Updated other dependencies
## 0.3.3
* `scan` `withServices` to filter by service UUID's (iOS)
* Error handled when trying to scan with adapter off (Android)
## 0.3.2
* Runtime permissions for Android
* `scan` `withServices` to filter by service UUID's (Android)
* Scan mode can be specified (Android)
* Now targets the latest android SDK
* Dart 2 compatibility
## 0.3.1
* Now allows simultaneous notifications of characteristics
* Fixed bug on iOS that was returning `discoverServices` too early
## 0.3.0
* iOS support added
* Bug fixed in example causing discoverServices to be called multiple times
* Various other bug fixes
## 0.2.4
* **Breaking change**. Upgraded to Gradle 4.1 and Android Studio Gradle plugin
3.0.1. Older Flutter projects need to upgrade their Gradle setup as well in
order to use this version of the plugin. Instructions can be found
[here](https://github.com/flutter/flutter/wiki/Updating-Flutter-projects-to-Gradle-4.1-and-Android-Studio-Gradle-plugin-3.0.1).
## 0.2.3
* Bug fixes
## 0.2.2
* **Breaking changes**:
* `startScan` renamed to `scan`
* `ScanResult` now returns a `BluetoothDevice`
* `connect` now takes a `BluetoothDevice` and returns Stream<BluetoothDeviceState>
* Added parameter `timeout` to `connect`
* Automatic disconnect on deviceConnection.cancel()
## 0.2.1
* **Breaking change**. Removed `stopScan` from API, use `scanSubscription.cancel()` instead
* Automatically stops scan when `startScan` subscription is canceled (thanks to @brianegan)
* Added `timeout` parameter to `startScan`
* Updated example app to show new scan functionality
## 0.2.0
* Added state and onStateChanged for BluetoothDevice
* Updated example to show new functionality
## 0.1.1
* Fixed image for pub.dartlang.org
## 0.1.0
* Characteristic notifications/indications.
* Merged in Guid library, removed from pubspec.yaml.
## 0.0.1 - September 1st, 2017
* Initial Release.

View File

@@ -0,0 +1 @@
TODO: Add your license here.

View File

@@ -0,0 +1,119 @@
<br>
<p align="center">
<img alt="FlutterBle" src="https://github.com/pauldemarco/flutter_blue/blob/master/site/flutterblue.png?raw=true" />
</p>
<br><br>
## Introduction
FlutterBle is a bluetooth plugin for [Flutter](http://www.flutter.io), a new mobile SDK to help developers build modern apps for iOS and Android.
fork of [FlutterBlue](https://github.com/pauldemarco/flutter_blue)
## Cross-Platform Bluetooth LE
FlutterBle aims to offer the most from both platforms (iOS and Android).
Using the FlutterBle instance, you can scan for and connect to nearby devices ([BluetoothDevice](#bluetoothdevice-api)).
Once connected to a device, the BluetoothDevice object can discover services ([BluetoothService](lib/src/bluetooth_service.dart)), characteristics ([BluetoothCharacteristic](lib/src/bluetooth_characteristic.dart)), and descriptors ([BluetoothDescriptor](lib/src/bluetooth_descriptor.dart)).
The BluetoothDevice object is then used to directly interact with characteristics and descriptors.
## Usage
### Obtain an instance
```dart
FlutterBle flutterBlue = FlutterBle.instance;
```
### Scan for devices
```dart
/// Start scanning
var scanSubscription = flutterBlue.scan().listen((scanResult) {
// do something with scan result
});
/// Stop scanning
scanSubscription.cancel();
```
### Connect to a device
```dart
/// Create a connection to the device
var deviceConnection = flutterBlue.connect(device).listen((s) {
if(s == BluetoothDeviceState.connected) {
// device is connected, do something
}
});
/// Disconnect from device
deviceConnection.cancel();
```
### Discover services
```dart
List<BluetoothService> services = await device.discoverServices();
services.forEach((service) {
// do something with service
});
```
### Read and write characteristics
```dart
// Reads all characteristics
var characteristics = service.characteristics;
for(BluetoothCharacteristic c in characteristics) {
List<int> value = await device.readCharacteristic(c);
print(value);
}
// Writes to a characteristic
await device.writeCharacteristic(c, [0x12, 0x34])
```
### Read and write descriptors
```dart
// Reads all descriptors
var descriptors = characteristic.descriptors;
for(BluetoothDescriptor d in descriptors) {
List<int> value = await device.readDescriptor(d);
print(value);
}
// Writes to a descriptor
await device.writeDescriptor(d, [0x12, 0x34])
```
### Set notifications
```dart
await device.setNotifyValue(characteristic, true);
device.onValueChanged(characteristic).listen((value) {
// do something with new value
});
```
## Reference
### FlutterBle API
| | Android | iOS | Description |
| :--------------- | :----------------: | :------------------: | :-------------------------------- |
| scan | :white_check_mark: | :white_check_mark: | Starts a scan for Bluetooth Low Energy devices. |
| connect | :white_check_mark: | :white_check_mark: | Establishes a connection to the Bluetooth Device. |
| state | :white_check_mark: | :white_check_mark: | Gets the current state of the Bluetooth Adapter. |
| onStateChanged | :white_check_mark: | :white_check_mark: | Stream of state changes for the Bluetooth Adapter. |
### BluetoothDevice API
| | Android | iOS | Description |
| :-------------------------- | :------------------: | :------------------: | :-------------------------------- |
| discoverServices | :white_check_mark: | :white_check_mark: | Discovers services offered by the remote device as well as their characteristics and descriptors. |
| services | :white_check_mark: | :white_check_mark: | Gets a list of services. Requires that discoverServices() has completed. |
| readCharacteristic | :white_check_mark: | :white_check_mark: | Retrieves the value of a specified characteristic. |
| readDescriptor | :white_check_mark: | :white_check_mark: | Retrieves the value of a specified descriptor. |
| writeCharacteristic | :white_check_mark: | :white_check_mark: | Writes the value of a characteristic. |
| writeDescriptor | :white_check_mark: | :white_check_mark: | Writes the value of a descriptor. |
| setNotifyValue | :white_check_mark: | :white_check_mark: | Sets notifications or indications on the specified characteristic. |
| onValueChanged | :white_check_mark: | :white_check_mark: | Notifies when the characteristic's value has changed. |
| state | :white_check_mark: | :white_check_mark: | Gets the current state of the Bluetooth Device. |
| onStateChanged | :white_check_mark: | :white_check_mark: | Notifies of state changes for the Bluetooth Device. |
## Troubleshooting
### Scanning for service UUID's doesn't return any results
Make sure the device is advertising which service UUID's it supports. This is found in the advertisement
packet as **UUID 16 bit complete list** or **UUID 128 bit complete list**.

View File

@@ -0,0 +1,8 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures

View File

@@ -0,0 +1,77 @@
group 'ai.longev.flutter.flutter_ble'
version '1.0-SNAPSHOT'
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.4.1'
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.8'
}
}
rootProject.allprojects {
repositories {
google()
jcenter()
}
}
apply plugin: 'com.android.library'
apply plugin: 'com.google.protobuf'
android {
compileSdkVersion 28
defaultConfig {
minSdkVersion 19
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
lintOptions {
disable 'InvalidPackage'
}
sourceSets {
main {
proto {
srcDir '../protos'
}
}
}
dependencies {
// Required for local unit tests (JUnit 4 framework)
implementation 'androidx.core:core:1.0.2'
}
}
protobuf {
// Configure the protoc executable
protoc {
// Download from repositories
artifact = 'com.google.protobuf:protoc:3.7.0'
}
plugins {
javalite { artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0'}
grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.19.0' }
}
generateProtoTasks {
all().each { task ->
task.plugins {
javalite {}
grpc {
// Options added to --grpc_out
option 'lite'
}
}
}
}
}
dependencies {
implementation 'com.google.protobuf:protobuf-lite:3.0.1'
}

View File

@@ -0,0 +1 @@
org.gradle.jvmargs=-Xmx1536M

View File

@@ -0,0 +1 @@
rootProject.name = 'flutter_ble'

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="ai.longev.flutter.flutter_ble">
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
</manifest>

View File

@@ -0,0 +1,106 @@
package ai.longev.flutter.flutter_ble;
import com.google.protobuf.ByteString;
import ai.longev.flutter.flutter_ble.Protos;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
/**
* Parser of Bluetooth Advertisement packets.
*/
class AdvertisementParser {
/**
* Parses packet data into {@link Protos.AdvertisementData} structure.
*
* @param rawData The scan record data.
* @return An AdvertisementData proto object.
* @throws ArrayIndexOutOfBoundsException if the input is truncated.
*/
static Protos.AdvertisementData parse(byte[] rawData) {
ByteBuffer data = ByteBuffer.wrap(rawData).asReadOnlyBuffer().order(ByteOrder.LITTLE_ENDIAN);
Protos.AdvertisementData.Builder ret = Protos.AdvertisementData.newBuilder();
boolean seenLongLocalName = false;
do {
int length = data.get() & 0xFF;
if (length == 0) {
break;
}
if (length > data.remaining()) {
throw new ArrayIndexOutOfBoundsException("Not enough data.");
}
int type = data.get() & 0xFF;
length--;
switch (type) {
case 0x08: // Short local name.
case 0x09: { // Long local name.
if (seenLongLocalName) {
// Prefer the long name over the short.
data.position(data.position() + length);
break;
}
byte[] name = new byte[length];
data.get(name);
ret.setLocalName(new String(name, StandardCharsets.UTF_8));
if (type == 0x09) {
seenLongLocalName = true;
}
break;
}
case 0x0A: { // Power level.
ret.setTxPowerLevel(Protos.Int32Value.newBuilder().setValue(data.get()));
break;
}
case 0x16: // Service Data with 16 bit UUID.
case 0x20: // Service Data with 32 bit UUID.
case 0x21: { // Service Data with 128 bit UUID.
UUID uuid;
int remainingDataLength = 0;
if (type == 0x16 || type == 0x20) {
long uuidValue;
if (type == 0x16) {
uuidValue = data.getShort() & 0xFFFF;
remainingDataLength = length - 2;
} else {
uuidValue = data.getInt() & 0xFFFFFFFF;
remainingDataLength = length - 4;
}
uuid = UUID.fromString(String.format("%08x-0000-1000-8000-00805f9b34fb", uuidValue));
} else {
long msb = data.getLong();
long lsb = data.getLong();
uuid = new UUID(msb, lsb);
remainingDataLength = length - 16;
}
byte[] remainingData = new byte[remainingDataLength];
data.get(remainingData);
ret.putServiceData(uuid.toString(), ByteString.copyFrom(remainingData));
break;
}
case 0xFF: {// Manufacturer specific data.
if (length < 2) {
throw new ArrayIndexOutOfBoundsException("Not enough data for Manufacturer specific data.");
}
int manufacturerId = data.getShort();
if ((length - 2) > 0) {
byte[] msd = new byte[length - 2];
data.get(msd);
ret.putManufacturerData(manufacturerId, ByteString.copyFrom(msd));
}
break;
}
default: {
data.position(data.position() + length);
break;
}
}
} while (true);
return ret.build();
}
}

View File

@@ -0,0 +1,884 @@
package ai.longev.flutter.flutter_ble;
// Copyright 2017, Paul DeMarco.
// All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import android.Manifest;
import android.annotation.TargetApi;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.ParcelUuid;
import android.util.Log;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.EventChannel.EventSink;
import io.flutter.plugin.common.EventChannel.StreamHandler;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.common.PluginRegistry.Registrar;
import io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener;
/**
* FlutterBlePlugin
*/
public class FlutterBlePlugin implements MethodCallHandler, RequestPermissionsResultListener {
private static final String TAG = "FlutterBlePlugin";
private static final String NAMESPACE = "ai.longev.flutter/flutter_ble";
private static final int REQUEST_COARSE_LOCATION_PERMISSIONS = 1452;
static final private UUID CCCD_ID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
private final Registrar registrar;
private final MethodChannel channel;
private final EventChannel stateChannel;
private final EventChannel scanResultChannel;
private final EventChannel servicesDiscoveredChannel;
private final EventChannel characteristicReadChannel;
private final EventChannel descriptorReadChannel;
private final BluetoothManager mBluetoothManager;
private final Map<String, BluetoothGatt> mGattServers = new HashMap<>();
private final StreamHandler stateHandler = new StreamHandler() {
private EventSink sink;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
BluetoothAdapter.ERROR);
switch (state) {
case BluetoothAdapter.STATE_OFF:
sink.success(Protos.BluetoothState.newBuilder().setState(Protos.BluetoothState.State.OFF).build().toByteArray());
break;
case BluetoothAdapter.STATE_TURNING_OFF:
sink.success(Protos.BluetoothState.newBuilder().setState(Protos.BluetoothState.State.TURNING_OFF).build().toByteArray());
break;
case BluetoothAdapter.STATE_ON:
sink.success(Protos.BluetoothState.newBuilder().setState(Protos.BluetoothState.State.ON).build().toByteArray());
break;
case BluetoothAdapter.STATE_TURNING_ON:
sink.success(Protos.BluetoothState.newBuilder().setState(Protos.BluetoothState.State.TURNING_ON).build().toByteArray());
break;
}
}
}
};
@Override
public void onListen(Object o, EventChannel.EventSink eventSink) {
sink = eventSink;
IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
registrar.activity().registerReceiver(mReceiver, filter);
}
@Override
public void onCancel(Object o) {
sink = null;
registrar.activity().unregisterReceiver(mReceiver);
}
};
private BluetoothAdapter mBluetoothAdapter;
private LogLevel logLevel = LogLevel.EMERGENCY;
// Pending call and result for startScan, in the case where permissions are needed
private MethodCall pendingCall;
private Result pendingResult;
private ScanCallback scanCallback21;
private BluetoothAdapter.LeScanCallback scanCallback18;
private EventSink scanResultsSink;
private final StreamHandler scanResultsHandler = new StreamHandler() {
@Override
public void onListen(Object o, EventChannel.EventSink eventSink) {
scanResultsSink = eventSink;
}
@Override
public void onCancel(Object o) {
scanResultsSink = null;
}
};
private EventSink servicesDiscoveredSink;
private final StreamHandler servicesDiscoveredHandler = new StreamHandler() {
@Override
public void onListen(Object o, EventChannel.EventSink eventSink) {
servicesDiscoveredSink = eventSink;
}
@Override
public void onCancel(Object o) {
servicesDiscoveredSink = null;
}
};
private EventSink characteristicReadSink;
private final StreamHandler characteristicReadHandler = new StreamHandler() {
@Override
public void onListen(Object o, EventChannel.EventSink eventSink) {
characteristicReadSink = eventSink;
}
@Override
public void onCancel(Object o) {
characteristicReadSink = null;
}
};
private EventSink descriptorReadSink;
private final StreamHandler descriptorReadHandler = new StreamHandler() {
@Override
public void onListen(Object o, EventChannel.EventSink eventSink) {
descriptorReadSink = eventSink;
}
@Override
public void onCancel(Object o) {
descriptorReadSink = null;
}
};
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
log(LogLevel.DEBUG, "[onConnectionStateChange] status: " + status + " newState: " + newState);
channel.invokeMethod("DeviceState", ProtoMaker.from(gatt.getDevice(), newState).toByteArray());
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
log(LogLevel.DEBUG, "[onServicesDiscovered] count: " + gatt.getServices().size() + " status: " + status);
if (servicesDiscoveredSink != null) {
Protos.DiscoverServicesResult.Builder p = Protos.DiscoverServicesResult.newBuilder();
p.setRemoteId(gatt.getDevice().getAddress());
for (BluetoothGattService s : gatt.getServices()) {
p.addServices(ProtoMaker.from(gatt.getDevice(), s, gatt));
}
servicesDiscoveredSink.success(p.build().toByteArray());
}
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
log(LogLevel.DEBUG, "[onCharacteristicRead] uuid: " + characteristic.getUuid().toString() + " status: " + status);
if (characteristicReadSink != null) {
Protos.ReadCharacteristicResponse.Builder p = Protos.ReadCharacteristicResponse.newBuilder();
p.setRemoteId(gatt.getDevice().getAddress());
p.setCharacteristic(ProtoMaker.from(characteristic, gatt));
characteristicReadSink.success(p.build().toByteArray());
}
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
log(LogLevel.DEBUG, "[onCharacteristicWrite] uuid: " + characteristic.getUuid().toString() + " status: " + status);
Protos.WriteCharacteristicRequest.Builder request = Protos.WriteCharacteristicRequest.newBuilder();
request.setRemoteId(gatt.getDevice().getAddress());
request.setCharacteristicUuid(characteristic.getUuid().toString());
request.setServiceUuid(characteristic.getService().getUuid().toString());
Protos.WriteCharacteristicResponse.Builder p = Protos.WriteCharacteristicResponse.newBuilder();
p.setRequest(request);
p.setSuccess(status == BluetoothGatt.GATT_SUCCESS);
channel.invokeMethod("WriteCharacteristicResponse", p.build().toByteArray());
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
log(LogLevel.DEBUG, "[onCharacteristicChanged] uuid: " + characteristic.getUuid().toString());
Protos.OnNotificationResponse.Builder p = Protos.OnNotificationResponse.newBuilder();
p.setRemoteId(gatt.getDevice().getAddress());
p.setCharacteristic(ProtoMaker.from(characteristic, gatt));
channel.invokeMethod("OnValueChanged", p.build().toByteArray());
}
@Override
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
log(LogLevel.DEBUG, "[onDescriptorRead] uuid: " + descriptor.getUuid().toString() + " status: " + status);
if (descriptorReadSink != null) {
// Rebuild the ReadAttributeRequest and send back along with response
Protos.ReadDescriptorRequest.Builder q = Protos.ReadDescriptorRequest.newBuilder();
q.setRemoteId(gatt.getDevice().getAddress());
q.setCharacteristicUuid(descriptor.getCharacteristic().getUuid().toString());
q.setDescriptorUuid(descriptor.getUuid().toString());
if (descriptor.getCharacteristic().getService().getType() == BluetoothGattService.SERVICE_TYPE_PRIMARY) {
q.setServiceUuid(descriptor.getCharacteristic().getService().getUuid().toString());
} else {
// Reverse search to find service
for (BluetoothGattService s : gatt.getServices()) {
for (BluetoothGattService ss : s.getIncludedServices()) {
if (ss.getUuid().equals(descriptor.getCharacteristic().getService().getUuid())) {
q.setServiceUuid(s.getUuid().toString());
q.setSecondaryServiceUuid(ss.getUuid().toString());
break;
}
}
}
}
Protos.ReadDescriptorResponse.Builder p = Protos.ReadDescriptorResponse.newBuilder();
p.setRequest(q);
p.setValue(ByteString.copyFrom(descriptor.getValue()));
descriptorReadSink.success(p.build().toByteArray());
}
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
log(LogLevel.DEBUG, "[onDescriptorWrite] uuid: " + descriptor.getUuid().toString() + " status: " + status);
Protos.WriteDescriptorRequest.Builder request = Protos.WriteDescriptorRequest.newBuilder();
request.setRemoteId(gatt.getDevice().getAddress());
request.setDescriptorUuid(descriptor.getUuid().toString());
request.setCharacteristicUuid(descriptor.getCharacteristic().getUuid().toString());
request.setServiceUuid(descriptor.getCharacteristic().getService().getUuid().toString());
Protos.WriteDescriptorResponse.Builder p = Protos.WriteDescriptorResponse.newBuilder();
p.setRequest(request);
p.setSuccess(status == BluetoothGatt.GATT_SUCCESS);
channel.invokeMethod("WriteDescriptorResponse", p.build().toByteArray());
if (descriptor.getUuid().compareTo(CCCD_ID) == 0) {
// SetNotificationResponse
Protos.SetNotificationResponse.Builder q = Protos.SetNotificationResponse.newBuilder();
q.setRemoteId(gatt.getDevice().getAddress());
q.setCharacteristic(ProtoMaker.from(descriptor.getCharacteristic(), gatt));
channel.invokeMethod("SetNotificationResponse", q.build().toByteArray());
}
}
@Override
public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
log(LogLevel.DEBUG, "[onReliableWriteCompleted] status: " + status);
}
@Override
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
log(LogLevel.DEBUG, "[onReadRemoteRssi] rssi: " + rssi + " status: " + status);
}
@Override
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
log(LogLevel.DEBUG, "[onMtuChanged] mtu: " + mtu + " status: " + status);
}
};
FlutterBlePlugin(Registrar r) {
this.registrar = r;
this.channel = new MethodChannel(registrar.messenger(), NAMESPACE + "/methods");
this.stateChannel = new EventChannel(registrar.messenger(), NAMESPACE + "/state");
this.scanResultChannel = new EventChannel(registrar.messenger(), NAMESPACE + "/scanResult");
this.servicesDiscoveredChannel = new EventChannel(registrar.messenger(), NAMESPACE + "/servicesDiscovered");
this.characteristicReadChannel = new EventChannel(registrar.messenger(), NAMESPACE + "/characteristicRead");
this.descriptorReadChannel = new EventChannel(registrar.messenger(), NAMESPACE + "/descriptorRead");
this.mBluetoothManager = (BluetoothManager) r.activity().getSystemService(Context.BLUETOOTH_SERVICE);
this.mBluetoothAdapter = mBluetoothManager.getAdapter();
channel.setMethodCallHandler(this);
stateChannel.setStreamHandler(stateHandler);
scanResultChannel.setStreamHandler(scanResultsHandler);
servicesDiscoveredChannel.setStreamHandler(servicesDiscoveredHandler);
characteristicReadChannel.setStreamHandler(characteristicReadHandler);
descriptorReadChannel.setStreamHandler(descriptorReadHandler);
}
/**
* Plugin registration.
*/
public static void registerWith(Registrar registrar) {
final FlutterBlePlugin instance = new FlutterBlePlugin(registrar);
registrar.addRequestPermissionsResultListener(instance);
}
@Override
public void onMethodCall(MethodCall call, Result result) {
if (mBluetoothAdapter == null && !"isAvailable".equals(call.method)) {
result.error("bluetooth_unavailable", "the device does not have bluetooth", null);
return;
}
switch (call.method) {
case "setLogLevel": {
int logLevelIndex = (int) call.arguments;
logLevel = LogLevel.values()[logLevelIndex];
result.success(null);
break;
}
case "state": {
Protos.BluetoothState.Builder p = Protos.BluetoothState.newBuilder();
try {
switch (mBluetoothAdapter.getState()) {
case BluetoothAdapter.STATE_OFF:
p.setState(Protos.BluetoothState.State.OFF);
break;
case BluetoothAdapter.STATE_ON:
p.setState(Protos.BluetoothState.State.ON);
break;
case BluetoothAdapter.STATE_TURNING_OFF:
p.setState(Protos.BluetoothState.State.TURNING_OFF);
break;
case BluetoothAdapter.STATE_TURNING_ON:
p.setState(Protos.BluetoothState.State.TURNING_ON);
break;
default:
p.setState(Protos.BluetoothState.State.UNKNOWN);
break;
}
} catch (SecurityException e) {
p.setState(Protos.BluetoothState.State.UNAUTHORIZED);
}
result.success(p.build().toByteArray());
break;
}
case "isAvailable": {
result.success(mBluetoothAdapter != null);
break;
}
case "isOn": {
result.success(mBluetoothAdapter.isEnabled());
break;
}
case "startScan": {
if (ContextCompat.checkSelfPermission(registrar.activity(), Manifest.permission.ACCESS_COARSE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
registrar.activity(),
new String[]{
Manifest.permission.ACCESS_COARSE_LOCATION
},
REQUEST_COARSE_LOCATION_PERMISSIONS);
pendingCall = call;
pendingResult = result;
break;
}
startScan(call, result);
break;
}
case "stopScan": {
stopScan();
result.success(null);
break;
}
case "connect": {
byte[] data = call.arguments();
Protos.ConnectRequest options;
try {
options = Protos.ConnectRequest.newBuilder().mergeFrom(data).build();
} catch (InvalidProtocolBufferException e) {
result.error("RuntimeException", e.getMessage(), e);
break;
}
String deviceId = options.getRemoteId();
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(deviceId);
boolean isConnected = mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT).contains(device);
// If device is already connected, return error
if (mGattServers.containsKey(deviceId) && isConnected) {
result.error("already_connected", "connection with device already exists", null);
return;
}
// If device was connected to previously but is now disconnected, attempt a reconnect
if (mGattServers.containsKey(deviceId) && !isConnected) {
if (!mGattServers.get(deviceId).connect()) {
result.error("reconnect_error", "error when reconnecting to device", null);
return;
}
}
// New request, connect and add gattServer to Map
BluetoothGatt gattServer = device.connectGatt(registrar.activity(), options.getAndroidAutoConnect(), mGattCallback);
mGattServers.put(deviceId, gattServer);
result.success(null);
break;
}
case "createBond": {
byte[] data = call.arguments();
Protos.ConnectRequest options;
try {
options = Protos.ConnectRequest.newBuilder().mergeFrom(data).build();
} catch (InvalidProtocolBufferException e) {
result.error("RuntimeException", e.getMessage(), e);
break;
}
String deviceId = options.getRemoteId();
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(deviceId);
boolean isConnected = mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT).contains(device);
if (mGattServers.containsKey(deviceId) && !isConnected) {
if (device.createBond()) {
result.success(null);
} else {
result.error(" bond error", "create bond error", null);
}
}
return;
}
case "disconnect": {
String deviceId = (String) call.arguments;
BluetoothGatt gattServer = mGattServers.remove(deviceId);
if (gattServer != null) {
gattServer.close();
}
result.success(null);
break;
}
case "deviceState": {
String deviceId = (String) call.arguments;
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(deviceId);
int state = mBluetoothManager.getConnectionState(device, BluetoothProfile.GATT);
try {
result.success(ProtoMaker.from(device, state).toByteArray());
} catch (Exception e) {
result.error("device_state_error", e.getMessage(), null);
}
break;
}
case "discoverServices": {
String deviceId = (String) call.arguments;
BluetoothGatt gattServer = mGattServers.get(deviceId);
if (gattServer == null) {
result.error("discover_services_error", "no instance of BluetoothGatt, have you connected first?", null);
return;
}
if (gattServer.discoverServices()) {
result.success(null);
} else {
result.error("discover_services_error", "unknown reason", null);
}
break;
}
case "services": {
String deviceId = (String) call.arguments;
BluetoothGatt gattServer = mGattServers.get(deviceId);
if (gattServer == null) {
result.error("get_services_error", "no instance of BluetoothGatt, have you connected first?", null);
return;
}
if (gattServer.getServices().isEmpty()) {
result.error("get_services_error", "services are empty, have you called discoverServices() yet?", null);
return;
}
Protos.DiscoverServicesResult.Builder p = Protos.DiscoverServicesResult.newBuilder();
p.setRemoteId(deviceId);
for (BluetoothGattService s : gattServer.getServices()) {
p.addServices(ProtoMaker.from(gattServer.getDevice(), s, gattServer));
}
result.success(p.build().toByteArray());
break;
}
case "readCharacteristic": {
byte[] data = call.arguments();
Protos.ReadCharacteristicRequest request;
try {
request = Protos.ReadCharacteristicRequest.newBuilder().mergeFrom(data).build();
} catch (InvalidProtocolBufferException e) {
result.error("RuntimeException", e.getMessage(), e);
break;
}
BluetoothGatt gattServer;
BluetoothGattCharacteristic characteristic;
try {
gattServer = locateGatt(request.getRemoteId());
characteristic = locateCharacteristic(gattServer, request.getServiceUuid(), request.getSecondaryServiceUuid(), request.getCharacteristicUuid());
} catch (Exception e) {
result.error("read_characteristic_error", e.getMessage(), null);
return;
}
if (gattServer.readCharacteristic(characteristic)) {
result.success(null);
} else {
result.error("read_characteristic_error", "unknown reason, may occur if readCharacteristic was called before last read finished.", null);
}
break;
}
case "readDescriptor": {
byte[] data = call.arguments();
Protos.ReadDescriptorRequest request;
try {
request = Protos.ReadDescriptorRequest.newBuilder().mergeFrom(data).build();
} catch (InvalidProtocolBufferException e) {
result.error("RuntimeException", e.getMessage(), e);
break;
}
BluetoothGatt gattServer;
BluetoothGattCharacteristic characteristic;
BluetoothGattDescriptor descriptor;
try {
gattServer = locateGatt(request.getRemoteId());
characteristic = locateCharacteristic(gattServer, request.getServiceUuid(), request.getSecondaryServiceUuid(), request.getCharacteristicUuid());
descriptor = locateDescriptor(characteristic, request.getDescriptorUuid());
} catch (Exception e) {
result.error("read_descriptor_error", e.getMessage(), null);
return;
}
if (gattServer.readDescriptor(descriptor)) {
result.success(null);
} else {
result.error("read_descriptor_error", "unknown reason, may occur if readDescriptor was called before last read finished.", null);
}
break;
}
case "writeCharacteristic": {
byte[] data = call.arguments();
Protos.WriteCharacteristicRequest request;
try {
request = Protos.WriteCharacteristicRequest.newBuilder().mergeFrom(data).build();
} catch (InvalidProtocolBufferException e) {
result.error("RuntimeException", e.getMessage(), e);
break;
}
BluetoothGatt gattServer;
BluetoothGattCharacteristic characteristic;
try {
gattServer = locateGatt(request.getRemoteId());
characteristic = locateCharacteristic(gattServer, request.getServiceUuid(), request.getSecondaryServiceUuid(), request.getCharacteristicUuid());
} catch (Exception e) {
result.error("write_characteristic_error", e.getMessage(), null);
return;
}
// Set characteristic to new value
if (!characteristic.setValue(request.getValue().toByteArray())) {
result.error("write_characteristic_error", "could not set the local value of characteristic", null);
}
// Apply the correct write type
if (request.getWriteType() == Protos.WriteCharacteristicRequest.WriteType.WITHOUT_RESPONSE) {
characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
} else {
characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
}
if (!gattServer.writeCharacteristic(characteristic)) {
result.error("write_characteristic_error", "writeCharacteristic failed", null);
return;
}
result.success(null);
break;
}
case "writeDescriptor": {
byte[] data = call.arguments();
Protos.WriteDescriptorRequest request;
try {
request = Protos.WriteDescriptorRequest.newBuilder().mergeFrom(data).build();
} catch (InvalidProtocolBufferException e) {
result.error("RuntimeException", e.getMessage(), e);
break;
}
BluetoothGatt gattServer;
BluetoothGattCharacteristic characteristic;
BluetoothGattDescriptor descriptor;
try {
gattServer = locateGatt(request.getRemoteId());
characteristic = locateCharacteristic(gattServer, request.getServiceUuid(), request.getSecondaryServiceUuid(), request.getCharacteristicUuid());
descriptor = locateDescriptor(characteristic, request.getDescriptorUuid());
} catch (Exception e) {
result.error("write_descriptor_error", e.getMessage(), null);
return;
}
// Set descriptor to new value
if (!descriptor.setValue(request.getValue().toByteArray())) {
result.error("write_descriptor_error", "could not set the local value for descriptor", null);
}
if (!gattServer.writeDescriptor(descriptor)) {
result.error("write_descriptor_error", "writeCharacteristic failed", null);
return;
}
result.success(null);
break;
}
case "setNotification": {
byte[] data = call.arguments();
Protos.SetNotificationRequest request;
try {
request = Protos.SetNotificationRequest.newBuilder().mergeFrom(data).build();
} catch (InvalidProtocolBufferException e) {
result.error("RuntimeException", e.getMessage(), e);
break;
}
BluetoothGatt gattServer;
BluetoothGattCharacteristic characteristic;
BluetoothGattDescriptor cccDescriptor;
try {
gattServer = locateGatt(request.getRemoteId());
characteristic = locateCharacteristic(gattServer, request.getServiceUuid(), request.getSecondaryServiceUuid(), request.getCharacteristicUuid());
cccDescriptor = characteristic.getDescriptor(CCCD_ID);
if (cccDescriptor == null) {
throw new Exception("could not locate CCCD descriptor for characteristic: " + characteristic.getUuid().toString());
}
} catch (Exception e) {
result.error("set_notification_error", e.getMessage(), null);
return;
}
byte[] value = null;
if (request.getEnable()) {
boolean canNotify = (characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0;
boolean canIndicate = (characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_INDICATE) > 0;
if (!canIndicate && !canNotify) {
result.error("set_notification_error", "the characteristic cannot notify or indicate", null);
return;
}
if (canIndicate) {
value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE;
}
if (canNotify) {
value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;
}
} else {
value = BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE;
}
if (!gattServer.setCharacteristicNotification(characteristic, request.getEnable())) {
result.error("set_notification_error", "could not set characteristic notifications to :" + request.getEnable(), null);
return;
}
if (!cccDescriptor.setValue(value)) {
result.error("set_notification_error", "error when setting the descriptor value to: " + value, null);
return;
}
if (!gattServer.writeDescriptor(cccDescriptor)) {
result.error("set_notification_error", "error when writing the descriptor", null);
return;
}
result.success(null);
break;
}
default: {
result.notImplemented();
break;
}
}
}
@Override
public boolean onRequestPermissionsResult(
int requestCode, String[] permissions, int[] grantResults) {
if (requestCode == REQUEST_COARSE_LOCATION_PERMISSIONS) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
startScan(pendingCall, pendingResult);
} else {
pendingResult.error(
"no_permissions", "flutter_blue plugin requires location permissions for scanning", null);
pendingResult = null;
pendingCall = null;
}
return true;
}
return false;
}
private BluetoothGatt locateGatt(String remoteId) throws Exception {
BluetoothGatt gattServer = mGattServers.get(remoteId);
if (gattServer == null) {
throw new Exception("no instance of BluetoothGatt, have you connected first?");
}
return gattServer;
}
private BluetoothGattCharacteristic locateCharacteristic(BluetoothGatt gattServer, String serviceId, String secondaryServiceId, String characteristicId) throws Exception {
BluetoothGattService primaryService = gattServer.getService(UUID.fromString(serviceId));
if (primaryService == null) {
throw new Exception("service (" + serviceId + ") could not be located on the device");
}
BluetoothGattService secondaryService = null;
if (secondaryServiceId.length() > 0) {
for (BluetoothGattService s : primaryService.getIncludedServices()) {
if (s.getUuid().equals(UUID.fromString(secondaryServiceId))) {
secondaryService = s;
}
}
if (secondaryService == null) {
throw new Exception("secondary service (" + secondaryServiceId + ") could not be located on the device");
}
}
BluetoothGattService service = (secondaryService != null) ? secondaryService : primaryService;
BluetoothGattCharacteristic characteristic = service.getCharacteristic(UUID.fromString(characteristicId));
if (characteristic == null) {
throw new Exception("characteristic (" + characteristicId + ") could not be located in the service (" + service.getUuid().toString() + ")");
}
return characteristic;
}
private BluetoothGattDescriptor locateDescriptor(BluetoothGattCharacteristic characteristic, String descriptorId) throws Exception {
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString(descriptorId));
if (descriptor == null) {
throw new Exception("descriptor (" + descriptorId + ") could not be located in the characteristic (" + characteristic.getUuid().toString() + ")");
}
return descriptor;
}
private void startScan(MethodCall call, Result result) {
byte[] data = call.arguments();
Protos.ScanSettings settings;
try {
settings = Protos.ScanSettings.newBuilder().mergeFrom(data).build();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
startScan21(settings);
} else {
startScan18(settings);
}
result.success(null);
} catch (Exception e) {
result.error("startScan", e.getMessage(), e);
}
}
private void stopScan() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
stopScan21();
} else {
stopScan18();
}
}
@TargetApi(21)
private ScanCallback getScanCallback21() {
if (scanCallback21 == null) {
scanCallback21 = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
if (scanResultsSink != null) {
Protos.ScanResult scanResult = ProtoMaker.from(result.getDevice(), result);
scanResultsSink.success(scanResult.toByteArray());
}
}
@Override
public void onBatchScanResults(List<ScanResult> results) {
super.onBatchScanResults(results);
}
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
}
};
}
return scanCallback21;
}
@TargetApi(21)
private void startScan21(Protos.ScanSettings proto) throws IllegalStateException {
BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner();
if (scanner == null)
throw new IllegalStateException("getBluetoothLeScanner() is null. Is the Adapter on?");
int scanMode = proto.getAndroidScanMode();
int count = proto.getServiceUuidsCount();
List<ScanFilter> filters = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
String uuid = proto.getServiceUuids(i);
ScanFilter f = new ScanFilter.Builder().setServiceUuid(ParcelUuid.fromString(uuid)).build();
filters.add(f);
}
ScanSettings settings = new ScanSettings.Builder().setScanMode(scanMode).build();
scanner.startScan(filters, settings, getScanCallback21());
}
@TargetApi(21)
private void stopScan21() {
BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner();
if (scanner != null) scanner.stopScan(getScanCallback21());
}
private BluetoothAdapter.LeScanCallback getScanCallback18() {
if (scanCallback18 == null) {
scanCallback18 = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice bluetoothDevice, int rssi,
byte[] scanRecord) {
if (scanResultsSink != null) {
Protos.ScanResult scanResult = ProtoMaker.from(bluetoothDevice, scanRecord, rssi);
scanResultsSink.success(scanResult.toByteArray());
}
}
};
}
return scanCallback18;
}
private void startScan18(Protos.ScanSettings proto) throws IllegalStateException {
List<String> serviceUuids = proto.getServiceUuidsList();
UUID[] uuids = new UUID[serviceUuids.size()];
for (int i = 0; i < serviceUuids.size(); i++) {
uuids[i] = UUID.fromString(serviceUuids.get(i));
}
boolean success = mBluetoothAdapter.startLeScan(uuids, getScanCallback18());
if (!success)
throw new IllegalStateException("getBluetoothLeScanner() is null. Is the Adapter on?");
}
private void stopScan18() {
mBluetoothAdapter.stopLeScan(getScanCallback18());
}
private void log(LogLevel level, String message) {
if (level.ordinal() <= logLevel.ordinal()) {
Log.d(TAG, message);
}
}
enum LogLevel {
EMERGENCY, ALERT, CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG
}
}

View File

@@ -0,0 +1,205 @@
package ai.longev.flutter.flutter_ble;
// Copyright 2017, Paul DeMarco.
// All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import android.annotation.TargetApi;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.ScanRecord;
import android.bluetooth.le.ScanResult;
import android.os.Build;
import android.os.ParcelUuid;
import android.util.SparseArray;
import com.google.protobuf.ByteString;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
* Created by paul on 8/31/17.
*/
public class ProtoMaker {
private static final UUID CCCD_UUID = UUID.fromString("000002902-0000-1000-8000-00805f9b34fb");
static Protos.ScanResult from(BluetoothDevice device, byte[] advertisementData, int rssi) {
Protos.ScanResult.Builder p = Protos.ScanResult.newBuilder();
p.setDevice(from(device));
if (advertisementData != null && advertisementData.length > 0)
p.setAdvertisementData(AdvertisementParser.parse(advertisementData));
p.setRssi(rssi);
return p.build();
}
@TargetApi(21)
static Protos.ScanResult from(BluetoothDevice device, ScanResult scanResult) {
Protos.ScanResult.Builder p = Protos.ScanResult.newBuilder();
p.setDevice(from(device));
Protos.AdvertisementData.Builder a = Protos.AdvertisementData.newBuilder();
ScanRecord scanRecord = scanResult.getScanRecord();
if (Build.VERSION.SDK_INT >= 26) {
a.setConnectable(scanResult.isConnectable());
} else {
if (scanRecord != null) {
int flags = scanRecord.getAdvertiseFlags();
a.setConnectable((flags & 0x2) > 0);
}
}
if (scanRecord != null) {
String deviceName = scanRecord.getDeviceName();
if (deviceName != null) {
a.setLocalName(deviceName);
}
int txPower = scanRecord.getTxPowerLevel();
if (txPower != Integer.MIN_VALUE) {
a.setTxPowerLevel(Protos.Int32Value.newBuilder().setValue(txPower));
}
// Manufacturer Specific Data
SparseArray<byte[]> msd = scanRecord.getManufacturerSpecificData();
for (int i = 0; i < msd.size(); i++) {
int key = msd.keyAt(i);
byte[] value = msd.valueAt(i);
a.putManufacturerData(key, ByteString.copyFrom(value));
}
// Service Data
Map<ParcelUuid, byte[]> serviceData = scanRecord.getServiceData();
for (Map.Entry<ParcelUuid, byte[]> entry : serviceData.entrySet()) {
ParcelUuid key = entry.getKey();
byte[] value = entry.getValue();
a.putServiceData(key.getUuid().toString(), ByteString.copyFrom(value));
}
// Service UUIDs
List<ParcelUuid> serviceUuids = scanRecord.getServiceUuids();
if (serviceUuids != null) {
for (ParcelUuid s : serviceUuids) {
a.addServiceUuids(s.getUuid().toString());
}
}
}
p.setRssi(scanResult.getRssi());
p.setAdvertisementData(a.build());
return p.build();
}
static Protos.BluetoothDevice from(BluetoothDevice device) {
Protos.BluetoothDevice.Builder p = Protos.BluetoothDevice.newBuilder();
p.setRemoteId(device.getAddress());
String name = device.getName();
if (name != null) {
p.setName(name);
}
switch (device.getType()) {
case BluetoothDevice.DEVICE_TYPE_LE:
p.setType(Protos.BluetoothDevice.Type.LE);
break;
case BluetoothDevice.DEVICE_TYPE_CLASSIC:
p.setType(Protos.BluetoothDevice.Type.CLASSIC);
break;
case BluetoothDevice.DEVICE_TYPE_DUAL:
p.setType(Protos.BluetoothDevice.Type.DUAL);
break;
default:
p.setType(Protos.BluetoothDevice.Type.UNKNOWN);
break;
}
return p.build();
}
static Protos.BluetoothService from(BluetoothDevice device, BluetoothGattService service, BluetoothGatt gatt) {
Protos.BluetoothService.Builder p = Protos.BluetoothService.newBuilder();
p.setRemoteId(device.getAddress());
p.setUuid(service.getUuid().toString());
p.setIsPrimary(service.getType() == BluetoothGattService.SERVICE_TYPE_PRIMARY);
for (BluetoothGattCharacteristic c : service.getCharacteristics()) {
p.addCharacteristics(from(c, gatt));
}
for (BluetoothGattService s : service.getIncludedServices()) {
p.addIncludedServices(from(device, s, gatt));
}
return p.build();
}
static Protos.BluetoothCharacteristic from(BluetoothGattCharacteristic characteristic, BluetoothGatt gatt) {
Protos.BluetoothCharacteristic.Builder p = Protos.BluetoothCharacteristic.newBuilder();
p.setUuid(characteristic.getUuid().toString());
p.setProperties(from(characteristic.getProperties()));
if (characteristic.getValue() != null)
p.setValue(ByteString.copyFrom(characteristic.getValue()));
for (BluetoothGattDescriptor d : characteristic.getDescriptors()) {
p.addDescriptors(from(d));
}
if (characteristic.getService().getType() == BluetoothGattService.SERVICE_TYPE_PRIMARY) {
p.setServiceUuid(characteristic.getService().getUuid().toString());
} else {
// Reverse search to find service
for (BluetoothGattService s : gatt.getServices()) {
for (BluetoothGattService ss : s.getIncludedServices()) {
if (ss.getUuid().equals(characteristic.getService().getUuid())) {
p.setServiceUuid(s.getUuid().toString());
p.setSecondaryServiceUuid(ss.getUuid().toString());
break;
}
}
}
}
return p.build();
}
static Protos.BluetoothDescriptor from(BluetoothGattDescriptor descriptor) {
Protos.BluetoothDescriptor.Builder p = Protos.BluetoothDescriptor.newBuilder();
p.setUuid(descriptor.getUuid().toString());
p.setCharacteristicUuid(descriptor.getCharacteristic().getUuid().toString());
p.setServiceUuid(descriptor.getCharacteristic().getService().getUuid().toString());
if (descriptor.getValue() != null)
p.setValue(ByteString.copyFrom(descriptor.getValue()));
return p.build();
}
static Protos.CharacteristicProperties from(int properties) {
return Protos.CharacteristicProperties.newBuilder()
.setBroadcast((properties & 1) != 0)
.setRead((properties & 2) != 0)
.setWriteWithoutResponse((properties & 4) != 0)
.setWrite((properties & 8) != 0)
.setNotify((properties & 16) != 0)
.setIndicate((properties & 32) != 0)
.setAuthenticatedSignedWrites((properties & 64) != 0)
.setExtendedProperties((properties & 128) != 0)
.setNotifyEncryptionRequired((properties & 256) != 0)
.setIndicateEncryptionRequired((properties & 512) != 0)
.build();
}
static Protos.DeviceStateResponse from(BluetoothDevice device, int state) {
Protos.DeviceStateResponse.Builder p = Protos.DeviceStateResponse.newBuilder();
switch (state) {
case BluetoothProfile.STATE_DISCONNECTING:
p.setState(Protos.DeviceStateResponse.BluetoothDeviceState.DISCONNECTING);
break;
case BluetoothProfile.STATE_CONNECTED:
p.setState(Protos.DeviceStateResponse.BluetoothDeviceState.CONNECTED);
break;
case BluetoothProfile.STATE_CONNECTING:
p.setState(Protos.DeviceStateResponse.BluetoothDeviceState.CONNECTING);
break;
case BluetoothProfile.STATE_DISCONNECTED:
p.setState(Protos.DeviceStateResponse.BluetoothDeviceState.DISCONNECTED);
break;
default:
break;
}
p.setRemoteId(device.getAddress());
return p.build();
}
}

View File

@@ -0,0 +1,70 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# Visual Studio Code related
.vscode/
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.packages
.pub-cache/
.pub/
/build/
# Android related
**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages

View File

@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 7a4c33425ddd78c54aba07d86f3f9a4a0051769b
channel: stable
project_type: app

View File

@@ -0,0 +1,16 @@
# flutter_ble_example
Demonstrates how to use the flutter_ble plugin.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
For help getting started with Flutter, view our
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

View File

@@ -0,0 +1,61 @@
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 28
lintOptions {
disable 'InvalidPackage'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "ai.longev.flutter.flutter_ble_example"
minSdkVersion 19
targetSdkVersion 28
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="ai.longev.flutter.flutter_ble_example">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@@ -0,0 +1,37 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="ai.longev.flutter.flutter_ble_example">
<uses-permission android:name="android.permission.INTERNET"/>
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<application
android:name="io.flutter.app.FlutterApplication"
android:label="flutter_ble_example"
android:icon="@mipmap/ic_launcher"
tools:ignore="GoogleAppIndexingWarning"
android:allowBackup="true">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- This keeps the window background of the activity showing
until Flutter renders its first frame. It can be removed if
there is no splash screen (such as the default splash screen
defined in @style/LaunchTheme). -->
<meta-data
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
android:value="true" />
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,13 @@
package ai.longev.flutter.flutter_ble_example;
import android.os.Bundle;
import io.flutter.app.FlutterActivity;
import io.flutter.plugins.GeneratedPluginRegistrant;
public class MainActivity extends FlutterActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
}
}

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
</resources>

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="ai.longev.flutter.flutter_ble_example">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@@ -0,0 +1,29 @@
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.4.1'
}
}
allprojects {
repositories {
google()
jcenter()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}

View File

@@ -0,0 +1,2 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true

View File

@@ -0,0 +1,6 @@
#Fri May 31 15:39:48 CDT 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip

View File

@@ -0,0 +1,15 @@
include ':app'
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
}
plugins.each { name, path ->
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
include ":$name"
project(":$name").projectDir = pluginDirectory
}

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>8.0</string>
</dict>
</plist>

View File

@@ -0,0 +1,2 @@
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"

View File

@@ -0,0 +1,2 @@
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"

View File

@@ -0,0 +1,69 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '9.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def parse_KV_file(file, separator='=')
file_abs_path = File.expand_path(file)
if !File.exists? file_abs_path
return [];
end
pods_ary = []
skip_line_start_symbols = ["#", "/"]
File.foreach(file_abs_path) { |line|
next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
plugin = line.split(pattern=separator)
if plugin.length == 2
podname = plugin[0].strip()
path = plugin[1].strip()
podpath = File.expand_path("#{path}", file_abs_path)
pods_ary.push({:name => podname, :path => podpath});
else
puts "Invalid plugin specification: #{line}"
end
}
return pods_ary
end
target 'Runner' do
# Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
# referring to absolute paths on developers' machines.
system('rm -rf .symlinks')
system('mkdir -p .symlinks/plugins')
# Flutter Pods
generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig')
if generated_xcode_build_settings.empty?
puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first."
end
generated_xcode_build_settings.map { |p|
if p[:name] == 'FLUTTER_FRAMEWORK_DIR'
symlink = File.join('.symlinks', 'flutter')
File.symlink(File.dirname(p[:path]), symlink)
pod 'Flutter', :path => File.join(symlink, File.basename(p[:path]))
end
}
# Plugin Pods
plugin_pods = parse_KV_file('../.flutter-plugins')
plugin_pods.map { |p|
symlink = File.join('.symlinks', 'plugins', p[:name])
File.symlink(p[:path], symlink)
pod p[:name], :path => File.join(symlink, 'ios')
}
end
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['ENABLE_BITCODE'] = 'NO'
end
end
end

View File

@@ -0,0 +1,38 @@
PODS:
- "!ProtoCompiler (3.7.0)":
- Protobuf (~> 3.0)
- Flutter (1.0.0)
- flutter_ble (0.5.1):
- "!ProtoCompiler"
- Flutter
- flutter_ble/Protos (= 0.5.1)
- flutter_ble/Protos (0.5.1):
- "!ProtoCompiler"
- Flutter
- Protobuf
- Protobuf (3.8.0)
DEPENDENCIES:
- Flutter (from `.symlinks/flutter/ios`)
- flutter_ble (from `.symlinks/plugins/flutter_ble/ios`)
SPEC REPOS:
https://github.com/cocoapods/specs.git:
- "!ProtoCompiler"
- Protobuf
EXTERNAL SOURCES:
Flutter:
:path: ".symlinks/flutter/ios"
flutter_ble:
:path: ".symlinks/plugins/flutter_ble/ios"
SPEC CHECKSUMS:
"!ProtoCompiler": efc8ba4dceb16b48f6b366a08500871c8d3ff8d2
Flutter: 58dd7d1b27887414a370fcccb9e645c08ffd7a6a
flutter_ble: c8480760bed31cb8337311c3faedfa9fcae5f9c1
Protobuf: 3f617b9a6e73605565086864c9bc26b2bf2dd5a3
PODFILE CHECKSUM: aff02bfeed411c636180d6812254b2daeea14d09
COCOAPODS: 1.7.0

View File

@@ -0,0 +1,577 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; };
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
A2A537F352EE5164B05911AB /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6CD001701BDC4728022DB42C /* libPods-Runner.a */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; };
6CD001701BDC4728022DB42C /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
7DD8CFB7F0364D55F5354FE1 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
CF6E4140832AD784A39F6B08 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
DE00DC1DC429064AF0B2580C /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
A2A537F352EE5164B05911AB /* libPods-Runner.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
3A903686E102B85427835225 /* Frameworks */ = {
isa = PBXGroup;
children = (
6CD001701BDC4728022DB42C /* libPods-Runner.a */,
);
name = Frameworks;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B80C3931E831B6300D905FE /* App.framework */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEBA1CF902C7004384FC /* Flutter.framework */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
D6EA32F9A89D2F62F8ABD1E7 /* Pods */,
3A903686E102B85427835225 /* Frameworks */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */,
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */,
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
97C146F11CF9000F007C117D /* Supporting Files */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
);
path = Runner;
sourceTree = "<group>";
};
97C146F11CF9000F007C117D /* Supporting Files */ = {
isa = PBXGroup;
children = (
97C146F21CF9000F007C117D /* main.m */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
D6EA32F9A89D2F62F8ABD1E7 /* Pods */ = {
isa = PBXGroup;
children = (
DE00DC1DC429064AF0B2580C /* Pods-Runner.debug.xcconfig */,
7DD8CFB7F0364D55F5354FE1 /* Pods-Runner.release.xcconfig */,
CF6E4140832AD784A39F6B08 /* Pods-Runner.profile.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
F4B005EC65297E62A1063D35 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
743D5EE8D0F9D613E1A63883 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0910;
ORGANIZATIONNAME = "The Chromium Authors";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
DevelopmentTeam = 3D56TJUQRV;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
English,
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
};
743D5EE8D0F9D613E1A63883 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
"${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
F4B005EC65297E62A1063D35 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */,
97C146F31CF9000F007C117D /* main.m in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 3D56TJUQRV;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = ai.longev.flutter.flutterBleExample;
PRODUCT_NAME = "$(TARGET_NAME)";
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 3D56TJUQRV;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = ai.longev.flutter.flutterBleExample;
PRODUCT_NAME = "$(TARGET_NAME)";
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 3D56TJUQRV;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = ai.longev.flutter.flutterBleExample;
PRODUCT_NAME = "$(TARGET_NAME)";
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0910"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildSystemType</key>
<string>Original</string>
</dict>
</plist>

View File

@@ -0,0 +1,6 @@
#import <Flutter/Flutter.h>
#import <UIKit/UIKit.h>
@interface AppDelegate : FlutterAppDelegate
@end

View File

@@ -0,0 +1,13 @@
#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
// Override point for customization after application launch.
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end

View File

@@ -0,0 +1,122 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@@ -0,0 +1,5 @@
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>flutter_ble_example</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>

View File

@@ -0,0 +1,9 @@
#import <Flutter/Flutter.h>
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char* argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

View File

@@ -0,0 +1,328 @@
// Copyright 2017, Paul DeMarco.
// All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_ble/flutter_ble.dart';
import 'package:flutter_ble_example/widgets.dart';
void main() {
runApp(new FlutterBleApp());
}
class FlutterBleApp extends StatefulWidget {
FlutterBleApp({Key key, this.title}) : super(key: key);
final String title;
@override
_FlutterBleAppState createState() => new _FlutterBleAppState();
}
class _FlutterBleAppState extends State<FlutterBleApp> {
FlutterBle _flutterBlue = FlutterBle.instance;
/// Scanning
StreamSubscription _scanSubscription;
Map<DeviceIdentifier, ScanResult> scanResults = new Map();
bool isScanning = false;
/// State
StreamSubscription _stateSubscription;
BluetoothState state = BluetoothState.unknown;
/// Device
BluetoothDevice device;
bool get isConnected => (device != null);
StreamSubscription deviceConnection;
StreamSubscription deviceStateSubscription;
List<BluetoothService> services = new List();
Map<Guid, StreamSubscription> valueChangedSubscriptions = {};
BluetoothDeviceState deviceState = BluetoothDeviceState.disconnected;
@override
void initState() {
super.initState();
// Immediately get the state of FlutterBle
_flutterBlue.state.then((s) {
setState(() {
state = s;
});
});
// Subscribe to state changes
_stateSubscription = _flutterBlue.onStateChanged().listen((s) {
setState(() {
state = s;
});
});
}
@override
void dispose() {
_stateSubscription?.cancel();
_stateSubscription = null;
_scanSubscription?.cancel();
_scanSubscription = null;
deviceConnection?.cancel();
deviceConnection = null;
super.dispose();
}
_startScan() {
_scanSubscription = _flutterBlue
.scan(
timeout: const Duration(seconds: 5),
/*withServices: [
new Guid('0000180F-0000-1000-8000-00805F9B34FB')
]*/
)
.listen((scanResult) {
print('localName: ${scanResult.advertisementData.localName}');
print(
'manufacturerData: ${scanResult.advertisementData.manufacturerData}');
print('serviceData: ${scanResult.advertisementData.serviceData}');
setState(() {
scanResults[scanResult.device.id] = scanResult;
});
}, onDone: _stopScan);
setState(() {
isScanning = true;
});
}
_stopScan() {
_scanSubscription?.cancel();
_scanSubscription = null;
setState(() {
isScanning = false;
});
}
_connect(BluetoothDevice d) async {
device = d;
// Connect to device
deviceConnection = _flutterBlue
.connect(device, timeout: const Duration(seconds: 4))
.listen(
null,
onDone: _disconnect,
);
// Update the connection state immediately
device.state.then((s) {
setState(() {
deviceState = s;
});
});
// Subscribe to connection changes
deviceStateSubscription = device.onStateChanged().listen((s) {
setState(() {
deviceState = s;
});
if (s == BluetoothDeviceState.connected) {
device.discoverServices().then((s) {
setState(() {
services = s;
});
});
}
});
}
_disconnect() {
// Remove all value changed listeners
valueChangedSubscriptions.forEach((uuid, sub) => sub.cancel());
valueChangedSubscriptions.clear();
deviceStateSubscription?.cancel();
deviceStateSubscription = null;
deviceConnection?.cancel();
deviceConnection = null;
setState(() {
device = null;
});
}
_readCharacteristic(BluetoothCharacteristic c) async {
await device.readCharacteristic(c);
setState(() {});
}
_writeCharacteristic(BluetoothCharacteristic c) async {
await device.writeCharacteristic(c, [0x12, 0x34],
type: CharacteristicWriteType.withResponse);
setState(() {});
}
_readDescriptor(BluetoothDescriptor d) async {
await device.readDescriptor(d);
setState(() {});
}
_writeDescriptor(BluetoothDescriptor d) async {
await device.writeDescriptor(d, [0x12, 0x34]);
setState(() {});
}
_setNotification(BluetoothCharacteristic c) async {
if (c.isNotifying) {
await device.setNotifyValue(c, false);
// Cancel subscription
valueChangedSubscriptions[c.uuid]?.cancel();
valueChangedSubscriptions.remove(c.uuid);
} else {
await device.setNotifyValue(c, true);
// ignore: cancel_subscriptions
final sub = device.onValueChanged(c).listen((d) {
setState(() {
print('onValueChanged $d');
});
});
// Add to map
valueChangedSubscriptions[c.uuid] = sub;
}
setState(() {});
}
_refreshDeviceState(BluetoothDevice d) async {
var state = await d.state;
setState(() {
deviceState = state;
print('State refreshed: $deviceState');
});
}
_buildScanningButton() {
if (isConnected || state != BluetoothState.on) {
return null;
}
if (isScanning) {
return new FloatingActionButton(
child: new Icon(Icons.stop),
onPressed: _stopScan,
backgroundColor: Colors.red,
);
} else {
return new FloatingActionButton(
child: new Icon(Icons.search), onPressed: _startScan);
}
}
_buildScanResultTiles() {
return scanResults.values
.map((r) => ScanResultTile(
result: r,
onTap: () => _connect(r.device),
))
.toList();
}
List<Widget> _buildServiceTiles() {
return services
.map(
(s) => new ServiceTile(
service: s,
characteristicTiles: s.characteristics
.map(
(c) => new CharacteristicTile(
characteristic: c,
onReadPressed: () => _readCharacteristic(c),
onWritePressed: () => _writeCharacteristic(c),
onNotificationPressed: () => _setNotification(c),
descriptorTiles: c.descriptors
.map(
(d) => new DescriptorTile(
descriptor: d,
onReadPressed: () => _readDescriptor(d),
onWritePressed: () =>
_writeDescriptor(d),
),
)
.toList(),
),
)
.toList(),
),
)
.toList();
}
_buildActionButtons() {
if (isConnected) {
return <Widget>[
new IconButton(
icon: const Icon(Icons.cancel),
onPressed: () => _disconnect(),
)
];
}
}
_buildAlertTile() {
return new Container(
color: Colors.redAccent,
child: new ListTile(
title: new Text(
'Bluetooth adapter is ${state.toString().substring(15)}',
style: Theme.of(context).primaryTextTheme.subhead,
),
trailing: new Icon(
Icons.error,
color: Theme.of(context).primaryTextTheme.subhead.color,
),
),
);
}
_buildDeviceStateTile() {
return new ListTile(
leading: (deviceState == BluetoothDeviceState.connected)
? const Icon(Icons.bluetooth_connected)
: const Icon(Icons.bluetooth_disabled),
title: new Text('Device is ${deviceState.toString().split('.')[1]}.'),
subtitle: new Text('${device.id}'),
trailing: new IconButton(
icon: const Icon(Icons.refresh),
onPressed: () => _refreshDeviceState(device),
color: Theme.of(context).iconTheme.color.withOpacity(0.5),
));
}
_buildProgressBarTile() {
return new LinearProgressIndicator();
}
@override
Widget build(BuildContext context) {
var tiles = new List<Widget>();
if (state != BluetoothState.on) {
tiles.add(_buildAlertTile());
}
if (isConnected) {
tiles.add(_buildDeviceStateTile());
tiles.addAll(_buildServiceTiles());
} else {
tiles.addAll(_buildScanResultTiles());
}
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: const Text('Flutter Longev BLE'),
actions: _buildActionButtons(),
),
floatingActionButton: _buildScanningButton(),
body: new Stack(
children: <Widget>[
(isScanning) ? _buildProgressBarTile() : new Container(),
new ListView(
children: tiles,
)
],
),
),
);
}
}

View File

@@ -0,0 +1,280 @@
// Copyright 2017, Paul DeMarco.
// All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter_ble/flutter_ble.dart';
class ScanResultTile extends StatelessWidget {
const ScanResultTile({Key key, this.result, this.onTap}) : super(key: key);
final ScanResult result;
final VoidCallback onTap;
Widget _buildTitle(BuildContext context) {
if (result.device.name.length > 0) {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(result.device.name),
Text(
result.device.id.toString(),
style: Theme.of(context).textTheme.caption,
)
],
);
} else {
return Text(result.device.id.toString());
}
}
Widget _buildAdvRow(BuildContext context, String title, String value) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 4.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(title, style: Theme.of(context).textTheme.caption),
SizedBox(
width: 12.0,
),
Expanded(
child: Text(
value,
style: Theme.of(context)
.textTheme
.caption
.apply(color: Colors.black),
softWrap: true,
),
),
],
),
);
}
String getNiceHexArray(List<int> bytes) {
return '[${bytes.map((i) => i.toRadixString(16).padLeft(2, '0')).join(', ')}]'
.toUpperCase();
}
String getNiceManufacturerData(Map<int, List<int>> data) {
if (data.isEmpty) {
return null;
}
List<String> res = [];
data.forEach((id, bytes) {
res.add(
'${id.toRadixString(16).toUpperCase()}: ${getNiceHexArray(bytes)}');
});
return res.join(', ');
}
String getNiceServiceData(Map<String, List<int>> data) {
if (data.isEmpty) {
return null;
}
List<String> res = [];
data.forEach((id, bytes) {
res.add('${id.toUpperCase()}: ${getNiceHexArray(bytes)}');
});
return res.join(', ');
}
@override
Widget build(BuildContext context) {
return ExpansionTile(
title: _buildTitle(context),
leading: Text(result.rssi.toString()),
trailing: RaisedButton(
child: Text('CONNECT'),
color: Colors.black,
textColor: Colors.white,
onPressed: (result.advertisementData.connectable) ? onTap : null,
),
children: <Widget>[
_buildAdvRow(
context, 'Complete Local Name', result.advertisementData.localName),
_buildAdvRow(context, 'Tx Power Level',
'${result.advertisementData.txPowerLevel ?? 'N/A'}'),
_buildAdvRow(
context,
'Manufacturer Data',
getNiceManufacturerData(
result.advertisementData.manufacturerData) ??
'N/A'),
_buildAdvRow(
context,
'Service UUIDs',
(result.advertisementData.serviceUuids.isNotEmpty)
? result.advertisementData.serviceUuids.join(', ').toUpperCase()
: 'N/A'),
_buildAdvRow(context, 'Service Data',
getNiceServiceData(result.advertisementData.serviceData) ?? 'N/A'),
],
);
}
}
class ServiceTile extends StatelessWidget {
final BluetoothService service;
final List<CharacteristicTile> characteristicTiles;
const ServiceTile({Key key, this.service, this.characteristicTiles})
: super(key: key);
@override
Widget build(BuildContext context) {
if (characteristicTiles.length > 0) {
return new ExpansionTile(
title: new Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const Text('Service'),
new Text(
'0x${service.uuid.toString().toUpperCase().substring(4, 8)}',
style: Theme.of(context)
.textTheme
.body1
.copyWith(color: Theme.of(context).textTheme.caption.color))
],
),
children: characteristicTiles,
);
} else {
return new ListTile(
title: const Text('Service'),
subtitle: new Text(
'0x${service.uuid.toString().toUpperCase().substring(4, 8)}'),
);
}
}
}
class CharacteristicTile extends StatelessWidget {
final BluetoothCharacteristic characteristic;
final List<DescriptorTile> descriptorTiles;
final VoidCallback onReadPressed;
final VoidCallback onWritePressed;
final VoidCallback onNotificationPressed;
const CharacteristicTile(
{Key key,
this.characteristic,
this.descriptorTiles,
this.onReadPressed,
this.onWritePressed,
this.onNotificationPressed})
: super(key: key);
@override
Widget build(BuildContext context) {
var actions = new Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new IconButton(
icon: new Icon(
Icons.file_download,
color: Theme.of(context).iconTheme.color.withOpacity(0.5),
),
onPressed: onReadPressed,
),
new IconButton(
icon: new Icon(Icons.file_upload,
color: Theme.of(context).iconTheme.color.withOpacity(0.5)),
onPressed: onWritePressed,
),
new IconButton(
icon: new Icon(
characteristic.isNotifying ? Icons.sync_disabled : Icons.sync,
color: Theme.of(context).iconTheme.color.withOpacity(0.5)),
onPressed: onNotificationPressed,
)
],
);
var title = new Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const Text('Characteristic'),
new Text(
'0x${characteristic.uuid.toString().toUpperCase().substring(4, 8)}',
style: Theme.of(context)
.textTheme
.body1
.copyWith(color: Theme.of(context).textTheme.caption.color))
],
);
if (descriptorTiles.length > 0) {
return new ExpansionTile(
title: new ListTile(
title: title,
subtitle: new Text(characteristic.value.toString()),
contentPadding: EdgeInsets.all(0.0),
),
trailing: actions,
children: descriptorTiles,
);
} else {
return new ListTile(
title: title,
subtitle: new Text(characteristic.value.toString()),
trailing: actions,
);
}
}
}
class DescriptorTile extends StatelessWidget {
final BluetoothDescriptor descriptor;
final VoidCallback onReadPressed;
final VoidCallback onWritePressed;
const DescriptorTile(
{Key key, this.descriptor, this.onReadPressed, this.onWritePressed})
: super(key: key);
@override
Widget build(BuildContext context) {
var title = new Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const Text('Descriptor'),
new Text(
'0x${descriptor.uuid.toString().toUpperCase().substring(4, 8)}',
style: Theme.of(context)
.textTheme
.body1
.copyWith(color: Theme.of(context).textTheme.caption.color))
],
);
return new ListTile(
title: title,
subtitle: new Text(descriptor.value.toString()),
trailing: new Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new IconButton(
icon: new Icon(
Icons.file_download,
color: Theme.of(context).iconTheme.color.withOpacity(0.5),
),
onPressed: onReadPressed,
),
new IconButton(
icon: new Icon(
Icons.file_upload,
color: Theme.of(context).iconTheme.color.withOpacity(0.5),
),
onPressed: onWritePressed,
)
],
),
);
}
}

View File

@@ -0,0 +1,174 @@
# Generated by pub
# See https://www.dartlang.org/tools/pub/glossary#lockfile
packages:
async:
dependency: transitive
description:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.4"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.2"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.14.11"
convert:
dependency: transitive
description:
name: convert
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.1"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.2"
fixnum:
dependency: transitive
description:
name: fixnum
url: "https://pub.dartlang.org"
source: hosted
version: "0.10.9"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_ble:
dependency: "direct dev"
description:
path: ".."
relative: true
source: path
version: "0.5.1"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
matcher:
dependency: transitive
description:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.5"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.6"
path:
dependency: transitive
description:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.6.2"
pedantic:
dependency: transitive
description:
name: pedantic
url: "https://pub.dartlang.org"
source: hosted
version: "1.5.0"
protobuf:
dependency: transitive
description:
name: protobuf
url: "https://pub.dartlang.org"
source: hosted
version: "0.13.11"
quiver:
dependency: transitive
description:
name: quiver
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.2"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_span:
dependency: transitive
description:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.5.5"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.dartlang.org"
source: hosted
version: "1.9.3"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
string_scanner:
dependency: transitive
description:
name: string_scanner
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.4"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.4"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.6"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.8"
sdks:
dart: ">=2.2.0 <3.0.0"

View File

@@ -0,0 +1,63 @@
name: flutter_ble_example
description: Demonstrates how to use the flutter_ble plugin.
publish_to: 'none'
environment:
sdk: ">=2.1.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.2
dev_dependencies:
flutter_test:
sdk: flutter
flutter_ble:
path: ../
# For information on the generic Dart part of this file, see the
# following page: https://www.dartlang.org/tools/pub/pubspec
# The following section is specific to Flutter.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware.
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages

View File

@@ -0,0 +1,27 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility that Flutter provides. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_ble_example/main.dart';
void main() {
testWidgets('Verify Platform version', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(FlutterBleApp());
// Verify that platform version is retrieved.
expect(
find.byWidgetPredicate(
(Widget widget) => widget is Text &&
widget.data.startsWith('Running on:'),
),
findsOneWidget,
);
});
}

View File

@@ -0,0 +1,36 @@
.idea/
.vagrant/
.sconsign.dblite
.svn/
.DS_Store
*.swp
profile
DerivedData/
build/
GeneratedPluginRegistrant.h
GeneratedPluginRegistrant.m
.generated/
*.pbxuser
*.mode1v3
*.mode2v3
*.perspectivev3
!default.pbxuser
!default.mode1v3
!default.mode2v3
!default.perspectivev3
xcuserdata
*.moved-aside
*.pyc
*sync/
Icon?
.tags*
/Flutter/Generated.xcconfig

View File

@@ -0,0 +1,11 @@
#import <Flutter/Flutter.h>
#import <CoreBluetooth/CoreBluetooth.h>
#define NAMESPACE @"ai.longev.flutter/flutter_ble"
@interface FlutterBlePlugin : NSObject<FlutterPlugin, CBCentralManagerDelegate, CBPeripheralDelegate>
@end
@interface FlutterBleStreamHandler : NSObject<FlutterStreamHandler>
@property FlutterEventSink sink;
@end

View File

@@ -0,0 +1,752 @@
// Copyright 2017, Paul DeMarco.
// All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#import "FlutterBlePlugin.h"
#import "Flutterblue.pbobjc.h"
@interface CBUUID (CBUUIDAdditionsFlutterBle)
- (NSString *)fullUUIDString;
@end
@implementation CBUUID (CBUUIDAdditionsFlutterBle)
- (NSString *)fullUUIDString {
if(self.UUIDString.length == 4) {
return [[NSString stringWithFormat:@"0000%@-0000-1000-8000-00805F9B34FB", self.UUIDString] lowercaseString];
}
return [self.UUIDString lowercaseString];
}
@end
typedef NS_ENUM(NSUInteger, LogLevel) {
emergency = 0,
alert = 1,
critical = 2,
error = 3,
warning = 4,
notice = 5,
info = 6,
debug = 7
};
@interface FlutterBlePlugin ()
@property(nonatomic, retain) NSObject<FlutterPluginRegistrar> *registrar;
@property(nonatomic, retain) FlutterMethodChannel *channel;
@property(nonatomic, retain) FlutterBleStreamHandler *stateStreamHandler;
@property(nonatomic, retain) FlutterBleStreamHandler *scanResultStreamHandler;
@property(nonatomic, retain) FlutterBleStreamHandler *servicesDiscoveredStreamHandler;
@property(nonatomic, retain) FlutterBleStreamHandler *characteristicReadStreamHandler;
@property(nonatomic, retain) FlutterBleStreamHandler *descriptorReadStreamHandler;
@property(nonatomic, retain) CBCentralManager *centralManager;
@property(nonatomic) NSMutableDictionary *scannedPeripherals;
@property(nonatomic) NSMutableArray *servicesThatNeedDiscovered;
@property(nonatomic) NSMutableArray *characteristicsThatNeedDiscovered;
@property(nonatomic) LogLevel logLevel;
@end
@implementation FlutterBlePlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
FlutterMethodChannel* channel = [FlutterMethodChannel
methodChannelWithName:NAMESPACE @"/methods"
binaryMessenger:[registrar messenger]];
FlutterEventChannel* stateChannel = [FlutterEventChannel eventChannelWithName:NAMESPACE @"/state" binaryMessenger:[registrar messenger]];
FlutterEventChannel* scanResultChannel = [FlutterEventChannel eventChannelWithName:NAMESPACE @"/scanResult" binaryMessenger:[registrar messenger]];
FlutterEventChannel* servicesDiscoveredChannel = [FlutterEventChannel eventChannelWithName:NAMESPACE @"/servicesDiscovered" binaryMessenger:[registrar messenger]];
FlutterEventChannel* characteristicReadChannel = [FlutterEventChannel eventChannelWithName:NAMESPACE @"/characteristicRead" binaryMessenger:[registrar messenger]];
FlutterEventChannel* descriptorReadChannel = [FlutterEventChannel eventChannelWithName:NAMESPACE @"/descriptorRead" binaryMessenger:[registrar messenger]];
FlutterBlePlugin* instance = [[FlutterBlePlugin alloc] init];
instance.channel = channel;
instance.centralManager = [[CBCentralManager alloc] initWithDelegate:instance queue:nil];
instance.scannedPeripherals = [NSMutableDictionary new];
instance.servicesThatNeedDiscovered = [NSMutableArray new];
instance.characteristicsThatNeedDiscovered = [NSMutableArray new];
instance.logLevel = emergency;
// STATE
FlutterBleStreamHandler* stateStreamHandler = [[FlutterBleStreamHandler alloc] init];
[stateChannel setStreamHandler:stateStreamHandler];
instance.stateStreamHandler = stateStreamHandler;
// SCAN RESULTS
FlutterBleStreamHandler* scanResultStreamHandler = [[FlutterBleStreamHandler alloc] init];
[scanResultChannel setStreamHandler:scanResultStreamHandler];
instance.scanResultStreamHandler = scanResultStreamHandler;
// SERVICES DISCOVERED
FlutterBleStreamHandler* servicesDiscoveredStreamHandler = [[FlutterBleStreamHandler alloc] init];
[servicesDiscoveredChannel setStreamHandler:servicesDiscoveredStreamHandler];
instance.servicesDiscoveredStreamHandler = servicesDiscoveredStreamHandler;
// CHARACTERISTIC READ
FlutterBleStreamHandler* characteristicReadStreamHandler = [[FlutterBleStreamHandler alloc] init];
[characteristicReadChannel setStreamHandler:characteristicReadStreamHandler];
instance.characteristicReadStreamHandler = characteristicReadStreamHandler;
// DESCRIPTOR READ
FlutterBleStreamHandler* descriptorReadStreamHandler = [[FlutterBleStreamHandler alloc] init];
[descriptorReadChannel setStreamHandler:descriptorReadStreamHandler];
instance.descriptorReadStreamHandler = descriptorReadStreamHandler;
[registrar addMethodCallDelegate:instance channel:channel];
}
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
if ([@"setLogLevel" isEqualToString:call.method]) {
NSNumber *logLevelIndex = [call arguments];
_logLevel = (LogLevel)[logLevelIndex integerValue];
result(nil);
} else if ([@"state" isEqualToString:call.method]) {
FlutterStandardTypedData *data = [self toFlutterData:[self toBluetoothStateProto:self->_centralManager.state]];
result(data);
} else if([@"isAvailable" isEqualToString:call.method]) {
if(self.centralManager.state != CBManagerStateUnsupported && self.centralManager.state != CBManagerStateUnknown) {
result(@(YES));
} else {
result(@(NO));
}
} else if([@"isOn" isEqualToString:call.method]) {
if(self.centralManager.state == CBManagerStatePoweredOn) {
result(@(YES));
} else {
result(@(NO));
}
} else if([@"startScan" isEqualToString:call.method]) {
// Clear any existing scan results
[self.scannedPeripherals removeAllObjects];
// TODO: Request Permission?
FlutterStandardTypedData *data = [call arguments];
ProtosScanSettings *request = [[ProtosScanSettings alloc] initWithData:[data data] error:nil];
// UUID Service filter
NSArray *uuids = [NSArray array];
for (int i = 0; i < [request serviceUuidsArray_Count]; i++) {
NSString *u = [request.serviceUuidsArray objectAtIndex:i];
uuids = [uuids arrayByAddingObject:[CBUUID UUIDWithString:u]];
}
// TODO: iOS Scan Options (#35)
[self->_centralManager scanForPeripheralsWithServices:uuids options:nil];
result(nil);
} else if([@"stopScan" isEqualToString:call.method]) {
[self->_centralManager stopScan];
result(nil);
} else if([@"connect" isEqualToString:call.method]) {
FlutterStandardTypedData *data = [call arguments];
ProtosConnectRequest *request = [[ProtosConnectRequest alloc] initWithData:[data data] error:nil];
NSString *remoteId = [request remoteId];
@try {
CBPeripheral *peripheral = [_scannedPeripherals objectForKey:remoteId];
if(peripheral == nil) {
@throw [FlutterError errorWithCode:@"connect"
message:@"Peripheral not found"
details:nil];
}
// TODO: Implement Connect options (#36)
[_centralManager connectPeripheral:peripheral options:nil];
result(nil);
} @catch(FlutterError *e) {
result(e);
}
} else if([@"disconnect" isEqualToString:call.method]) {
NSString *remoteId = [call arguments];
@try {
CBPeripheral *peripheral = [self findPeripheral:remoteId];
[_centralManager cancelPeripheralConnection:peripheral];
result(nil);
} @catch(FlutterError *e) {
result(e);
}
} else if([@"deviceState" isEqualToString:call.method]) {
NSString *remoteId = [call arguments];
@try {
CBPeripheral *peripheral = [self findPeripheral:remoteId];
result([self toFlutterData:[self toDeviceStateProto:peripheral state:peripheral.state]]);
} @catch(FlutterError *e) {
result(e);
}
} else if([@"discoverServices" isEqualToString:call.method]) {
NSString *remoteId = [call arguments];
@try {
CBPeripheral *peripheral = [self findPeripheral:remoteId];
// Clear helper arrays
[_servicesThatNeedDiscovered removeAllObjects];
[_characteristicsThatNeedDiscovered removeAllObjects ];
[peripheral discoverServices:nil];
result(nil);
} @catch(FlutterError *e) {
result(e);
}
} else if([@"services" isEqualToString:call.method]) {
NSString *remoteId = [call arguments];
@try {
CBPeripheral *peripheral = [self findPeripheral:remoteId];
result([self toFlutterData:[self toServicesResultProto:peripheral]]);
} @catch(FlutterError *e) {
result(e);
}
} else if([@"readCharacteristic" isEqualToString:call.method]) {
FlutterStandardTypedData *data = [call arguments];
ProtosReadCharacteristicRequest *request = [[ProtosReadCharacteristicRequest alloc] initWithData:[data data] error:nil];
NSString *remoteId = [request remoteId];
@try {
// Find peripheral
CBPeripheral *peripheral = [self findPeripheral:remoteId];
// Find characteristic
CBCharacteristic *characteristic = [self locateCharacteristic:[request characteristicUuid] peripheral:peripheral serviceId:[request serviceUuid] secondaryServiceId:[request secondaryServiceUuid]];
// Trigger a read
[peripheral readValueForCharacteristic:characteristic];
result(nil);
} @catch(FlutterError *e) {
result(e);
}
} else if([@"readDescriptor" isEqualToString:call.method]) {
FlutterStandardTypedData *data = [call arguments];
ProtosReadDescriptorRequest *request = [[ProtosReadDescriptorRequest alloc] initWithData:[data data] error:nil];
NSString *remoteId = [request remoteId];
@try {
// Find peripheral
CBPeripheral *peripheral = [self findPeripheral:remoteId];
// Find characteristic
CBCharacteristic *characteristic = [self locateCharacteristic:[request characteristicUuid] peripheral:peripheral serviceId:[request serviceUuid] secondaryServiceId:[request secondaryServiceUuid]];
// Find descriptor
CBDescriptor *descriptor = [self locateDescriptor:[request descriptorUuid] characteristic:characteristic];
[peripheral readValueForDescriptor:descriptor];
result(nil);
} @catch(FlutterError *e) {
result(e);
}
} else if([@"writeCharacteristic" isEqualToString:call.method]) {
FlutterStandardTypedData *data = [call arguments];
ProtosWriteCharacteristicRequest *request = [[ProtosWriteCharacteristicRequest alloc] initWithData:[data data] error:nil];
NSString *remoteId = [request remoteId];
@try {
// Find peripheral
CBPeripheral *peripheral = [self findPeripheral:remoteId];
// Find characteristic
CBCharacteristic *characteristic = [self locateCharacteristic:[request characteristicUuid] peripheral:peripheral serviceId:[request serviceUuid] secondaryServiceId:[request secondaryServiceUuid]];
// Get correct write type
CBCharacteristicWriteType type = ([request writeType] == ProtosWriteCharacteristicRequest_WriteType_WithoutResponse) ? CBCharacteristicWriteWithoutResponse : CBCharacteristicWriteWithResponse;
// Write to characteristic
[peripheral writeValue:[request value] forCharacteristic:characteristic type:type];
result(nil);
} @catch(FlutterError *e) {
result(e);
}
} else if([@"writeDescriptor" isEqualToString:call.method]) {
FlutterStandardTypedData *data = [call arguments];
ProtosWriteDescriptorRequest *request = [[ProtosWriteDescriptorRequest alloc] initWithData:[data data] error:nil];
NSString *remoteId = [request remoteId];
@try {
// Find peripheral
CBPeripheral *peripheral = [self findPeripheral:remoteId];
// Find characteristic
CBCharacteristic *characteristic = [self locateCharacteristic:[request characteristicUuid] peripheral:peripheral serviceId:[request serviceUuid] secondaryServiceId:[request secondaryServiceUuid]];
// Find descriptor
CBDescriptor *descriptor = [self locateDescriptor:[request descriptorUuid] characteristic:characteristic];
// Write descriptor
[peripheral writeValue:[request value] forDescriptor:descriptor];
result(nil);
} @catch(FlutterError *e) {
result(e);
}
} else if([@"setNotification" isEqualToString:call.method]) {
FlutterStandardTypedData *data = [call arguments];
ProtosSetNotificationRequest *request = [[ProtosSetNotificationRequest alloc] initWithData:[data data] error:nil];
NSString *remoteId = [request remoteId];
@try {
// Find peripheral
CBPeripheral *peripheral = [self findPeripheral:remoteId];
// Find characteristic
CBCharacteristic *characteristic = [self locateCharacteristic:[request characteristicUuid] peripheral:peripheral serviceId:[request serviceUuid] secondaryServiceId:[request secondaryServiceUuid]];
// Set notification value
[peripheral setNotifyValue:[request enable] forCharacteristic:characteristic];
result(nil);
} @catch(FlutterError *e) {
result(e);
}
} else {
result(FlutterMethodNotImplemented);
}
}
- (CBPeripheral*)findPeripheral:(NSString*)remoteId {
NSArray<CBPeripheral*> *peripherals = [_centralManager retrievePeripheralsWithIdentifiers:@[[[NSUUID alloc] initWithUUIDString:remoteId]]];
CBPeripheral *peripheral;
for(CBPeripheral *p in peripherals) {
if([[p.identifier UUIDString] isEqualToString:remoteId]) {
peripheral = p;
break;
}
}
if(peripheral == nil) {
@throw [FlutterError errorWithCode:@"findPeripheral"
message:@"Peripheral not found"
details:nil];
}
return peripheral;
}
- (CBCharacteristic*)locateCharacteristic:(NSString*)characteristicId peripheral:(CBPeripheral*)peripheral serviceId:(NSString*)serviceId secondaryServiceId:(NSString*)secondaryServiceId {
CBService *primaryService = [self getServiceFromArray:serviceId array:[peripheral services]];
if(primaryService == nil || [primaryService isPrimary] == false) {
@throw [FlutterError errorWithCode:@"locateCharacteristic"
message:@"service could not be located on the device"
details:nil];
}
CBService *secondaryService;
if(secondaryServiceId.length) {
secondaryService = [self getServiceFromArray:secondaryServiceId array:[primaryService includedServices]];
@throw [FlutterError errorWithCode:@"locateCharacteristic"
message:@"secondary service could not be located on the device"
details:secondaryServiceId];
}
CBService *service = (secondaryService != nil) ? secondaryService : primaryService;
CBCharacteristic *characteristic = [self getCharacteristicFromArray:characteristicId array:[service characteristics]];
if(characteristic == nil) {
@throw [FlutterError errorWithCode:@"locateCharacteristic"
message:@"characteristic could not be located on the device"
details:nil];
}
return characteristic;
}
- (CBDescriptor*)locateDescriptor:(NSString*)descriptorId characteristic:(CBCharacteristic*)characteristic {
CBDescriptor *descriptor = [self getDescriptorFromArray:descriptorId array:[characteristic descriptors]];
if(descriptor == nil) {
@throw [FlutterError errorWithCode:@"locateDescriptor"
message:@"descriptor could not be located on the device"
details:nil];
}
return descriptor;
}
// Reverse search to find primary service
- (CBService*)findPrimaryService:(CBService*)secondaryService peripheral:(CBPeripheral*)peripheral {
for(CBService *s in [peripheral services]) {
for(CBService *ss in [s includedServices]) {
if([[ss.UUID UUIDString] isEqualToString:[secondaryService.UUID UUIDString]]) {
return s;
}
}
}
return nil;
}
- (CBDescriptor*)findCCCDescriptor:(CBCharacteristic*)characteristic {
for(CBDescriptor *d in characteristic.descriptors) {
if([d.UUID.UUIDString isEqualToString:@"2902"]) {
return d;
}
}
return nil;
}
- (CBService*)getServiceFromArray:(NSString*)uuidString array:(NSArray<CBService*>*)array {
for(CBService *s in array) {
if([[s UUID] isEqual:[CBUUID UUIDWithString:uuidString]]) {
return s;
}
}
return nil;
}
- (CBCharacteristic*)getCharacteristicFromArray:(NSString*)uuidString array:(NSArray<CBCharacteristic*>*)array {
for(CBCharacteristic *c in array) {
if([[c UUID] isEqual:[CBUUID UUIDWithString:uuidString]]) {
return c;
}
}
return nil;
}
- (CBDescriptor*)getDescriptorFromArray:(NSString*)uuidString array:(NSArray<CBDescriptor*>*)array {
for(CBDescriptor *d in array) {
if([[d UUID] isEqual:[CBUUID UUIDWithString:uuidString]]) {
return d;
}
}
return nil;
}
//
// CBCentralManagerDelegate methods
//
- (void)centralManagerDidUpdateState:(nonnull CBCentralManager *)central {
if(_stateStreamHandler.sink != nil) {
FlutterStandardTypedData *data = [self toFlutterData:[self toBluetoothStateProto:self->_centralManager.state]];
self.stateStreamHandler.sink(data);
}
}
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI {
[self.scannedPeripherals setObject:peripheral
forKey:[[peripheral identifier] UUIDString]];
if(_scanResultStreamHandler.sink != nil) {
FlutterStandardTypedData *data = [self toFlutterData:[self toScanResultProto:peripheral advertisementData:advertisementData RSSI:RSSI]];
_scanResultStreamHandler.sink(data);
}
}
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
NSLog(@"didConnectPeripheral");
// Register self as delegate for peripheral
peripheral.delegate = self;
// Send connection state
[_channel invokeMethod:@"DeviceState" arguments:[self toFlutterData:[self toDeviceStateProto:peripheral state:peripheral.state]]];
}
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
NSLog(@"didDisconnectPeripheral");
// Unregister self as delegate for peripheral, not working #42
peripheral.delegate = nil;
// Send connection state
[_channel invokeMethod:@"DeviceState" arguments:[self toFlutterData:[self toDeviceStateProto:peripheral state:peripheral.state]]];
}
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
// TODO:?
}
//
// CBPeripheralDelegate methods
//
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
NSLog(@"didDiscoverServices");
// Loop through and discover characteristics and secondary services
[_servicesThatNeedDiscovered addObjectsFromArray:peripheral.services];
for(CBService *s in [peripheral services]) {
NSLog(@"Found service: %@", [s.UUID UUIDString]);
[peripheral discoverCharacteristics:nil forService:s];
// [peripheral discoverIncludedServices:nil forService:s]; // Secondary services in the future (#8)
}
}
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
NSLog(@"didDiscoverCharacteristicsForService");
// Loop through and discover descriptors for characteristics
[_servicesThatNeedDiscovered removeObject:service];
[_characteristicsThatNeedDiscovered addObjectsFromArray:service.characteristics];
for(CBCharacteristic *c in [service characteristics]) {
[peripheral discoverDescriptorsForCharacteristic:c];
}
}
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
NSLog(@"didDiscoverDescriptorsForCharacteristic");
[_characteristicsThatNeedDiscovered removeObject:characteristic];
if(_servicesThatNeedDiscovered.count > 0 || _characteristicsThatNeedDiscovered.count > 0) {
// Still discovering
return;
}
// Send updated tree
if(_servicesDiscoveredStreamHandler.sink != nil) {
ProtosDiscoverServicesResult *result = [self toServicesResultProto:peripheral];
_servicesDiscoveredStreamHandler.sink([self toFlutterData:result]);
}
}
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverIncludedServicesForService:(CBService *)service error:(NSError *)error {
NSLog(@"didDiscoverIncludedServicesForService");
// Loop through and discover characteristics for secondary services
for(CBService *ss in [service includedServices]) {
[peripheral discoverCharacteristics:nil forService:ss];
}
}
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
NSLog(@"didUpdateValueForCharacteristic %@", [peripheral.identifier UUIDString]);
if(_characteristicReadStreamHandler.sink != nil) {
ProtosReadCharacteristicResponse *result = [[ProtosReadCharacteristicResponse alloc] init];
[result setRemoteId:[peripheral.identifier UUIDString]];
[result setCharacteristic:[self toCharacteristicProto:characteristic]];
_characteristicReadStreamHandler.sink([self toFlutterData:result]);
}
// on iOS, this method also handle notification values
ProtosOnNotificationResponse *result = [[ProtosOnNotificationResponse alloc] init];
[result setRemoteId:[peripheral.identifier UUIDString]];
[result setCharacteristic:[self toCharacteristicProto:characteristic]];
[_channel invokeMethod:@"OnValueChanged" arguments:[self toFlutterData:result]];
}
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
NSLog(@"didWriteValueForCharacteristic");
ProtosWriteCharacteristicRequest *request = [[ProtosWriteCharacteristicRequest alloc] init];
[request setRemoteId:[peripheral.identifier UUIDString]];
[request setCharacteristicUuid:[characteristic.UUID fullUUIDString]];
[request setServiceUuid:[characteristic.service.UUID fullUUIDString]];
ProtosWriteCharacteristicResponse *result = [[ProtosWriteCharacteristicResponse alloc] init];
[result setRequest:request];
[result setSuccess:(error == nil)];
[_channel invokeMethod:@"WriteCharacteristicResponse" arguments:[self toFlutterData:result]];
}
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
NSLog(@"didUpdateNotificationStateForCharacteristic");
// Read CCC descriptor of characteristic
CBDescriptor *cccd = [self findCCCDescriptor:characteristic];
if(cccd == nil || error != nil) {
// Send error
ProtosSetNotificationResponse *response = [[ProtosSetNotificationResponse alloc] init];
[response setRemoteId:[peripheral.identifier UUIDString]];
[response setCharacteristic:[self toCharacteristicProto:characteristic]];
[response setSuccess:false];
[_channel invokeMethod:@"SetNotificationResponse" arguments:[self toFlutterData:response]];
return;
}
// Request a read
[peripheral readValueForDescriptor:cccd];
}
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForDescriptor:(CBDescriptor *)descriptor error:(NSError *)error {
if(_descriptorReadStreamHandler.sink != nil) {
ProtosReadDescriptorRequest *q = [[ProtosReadDescriptorRequest alloc] init];
[q setRemoteId:[peripheral.identifier UUIDString]];
[q setCharacteristicUuid:[descriptor.characteristic.UUID fullUUIDString]];
[q setDescriptorUuid:[descriptor.UUID fullUUIDString]];
if([descriptor.characteristic.service isPrimary]) {
[q setServiceUuid:[descriptor.characteristic.service.UUID fullUUIDString]];
} else {
[q setSecondaryServiceUuid:[descriptor.characteristic.service.UUID fullUUIDString]];
CBService *primaryService = [self findPrimaryService:[descriptor.characteristic service] peripheral:[descriptor.characteristic.service peripheral]];
[q setServiceUuid:[primaryService.UUID fullUUIDString]];
}
ProtosReadDescriptorResponse *result = [[ProtosReadDescriptorResponse alloc] init];
[result setRequest:q];
int value = [descriptor.value intValue];
[result setValue:[NSData dataWithBytes:&value length:sizeof(value)]];
_descriptorReadStreamHandler.sink([self toFlutterData:result]);
}
// If descriptor is CCCD, send a SetNotificationResponse in case anything is awaiting
if([descriptor.UUID.UUIDString isEqualToString:@"2902"]){
ProtosSetNotificationResponse *response = [[ProtosSetNotificationResponse alloc] init];
[response setRemoteId:[peripheral.identifier UUIDString]];
[response setCharacteristic:[self toCharacteristicProto:descriptor.characteristic]];
[response setSuccess:true];
[_channel invokeMethod:@"SetNotificationResponse" arguments:[self toFlutterData:response]];
}
}
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForDescriptor:(CBDescriptor *)descriptor error:(NSError *)error {
ProtosWriteDescriptorRequest *request = [[ProtosWriteDescriptorRequest alloc] init];
[request setRemoteId:[peripheral.identifier UUIDString]];
[request setCharacteristicUuid:[descriptor.characteristic.UUID fullUUIDString]];
[request setDescriptorUuid:[descriptor.UUID fullUUIDString]];
if([descriptor.characteristic.service isPrimary]) {
[request setServiceUuid:[descriptor.characteristic.service.UUID fullUUIDString]];
} else {
[request setSecondaryServiceUuid:[descriptor.characteristic.service.UUID fullUUIDString]];
CBService *primaryService = [self findPrimaryService:[descriptor.characteristic service] peripheral:[descriptor.characteristic.service peripheral]];
[request setServiceUuid:[primaryService.UUID fullUUIDString]];
}
ProtosWriteDescriptorResponse *result = [[ProtosWriteDescriptorResponse alloc] init];
[result setRequest:request];
[result setSuccess:(error == nil)];
[_channel invokeMethod:@"WriteDescriptorResponse" arguments:[self toFlutterData:result]];
}
//
// Proto Helper methods
//
- (FlutterStandardTypedData*)toFlutterData:(GPBMessage*)proto {
FlutterStandardTypedData *data = [FlutterStandardTypedData typedDataWithBytes:[[proto data] copy]];
return data;
}
- (ProtosBluetoothState*)toBluetoothStateProto:(CBManagerState)state {
ProtosBluetoothState *result = [[ProtosBluetoothState alloc] init];
switch(state) {
case CBManagerStateResetting:
[result setState:ProtosBluetoothState_State_TurningOn];
break;
case CBManagerStateUnsupported:
[result setState:ProtosBluetoothState_State_Unavailable];
break;
case CBManagerStateUnauthorized:
[result setState:ProtosBluetoothState_State_Unauthorized];
break;
case CBManagerStatePoweredOff:
[result setState:ProtosBluetoothState_State_Off];
break;
case CBManagerStatePoweredOn:
[result setState:ProtosBluetoothState_State_On];
break;
default:
[result setState:ProtosBluetoothState_State_Unknown];
break;
}
return result;
}
- (ProtosScanResult*)toScanResultProto:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI {
ProtosScanResult *result = [[ProtosScanResult alloc] init];
[result setDevice:[self toDeviceProto:peripheral]];
[result setRssi:[RSSI intValue]];
ProtosAdvertisementData *ads = [[ProtosAdvertisementData alloc] init];
[ads setConnectable:[advertisementData[CBAdvertisementDataIsConnectable] boolValue]];
[ads setLocalName:advertisementData[CBAdvertisementDataLocalNameKey]];
// Tx Power Level
NSNumber *txPower = advertisementData[CBAdvertisementDataTxPowerLevelKey];
if(txPower != nil) {
ProtosInt32Value *txPowerWrapper = [[ProtosInt32Value alloc] init];
[txPowerWrapper setValue:[txPower intValue]];
[ads setTxPowerLevel:txPowerWrapper];
}
// Manufacturer Specific Data
NSData *manufData = advertisementData[CBAdvertisementDataManufacturerDataKey];
if(manufData.length > 2) {
unsigned short manufacturerId;
[manufData getBytes:&manufacturerId length:2];
[[ads manufacturerData] setObject:[manufData subdataWithRange:NSMakeRange(2, manufData.length - 2)] forKey:manufacturerId];
}
// Service Data
NSDictionary *serviceData = advertisementData[CBAdvertisementDataServiceDataKey];
for (CBUUID *uuid in serviceData) {
[[ads serviceData] setObject:serviceData[uuid] forKey:uuid.UUIDString];
}
// Service Uuids
NSArray *serviceUuids = advertisementData[CBAdvertisementDataServiceUUIDsKey];
for (CBUUID *uuid in serviceUuids) {
[[ads serviceUuidsArray] addObject:uuid.UUIDString];
}
[result setAdvertisementData:ads];
return result;
}
- (ProtosBluetoothDevice*)toDeviceProto:(CBPeripheral *)peripheral {
ProtosBluetoothDevice *result = [[ProtosBluetoothDevice alloc] init];
[result setName:[peripheral name]];
[result setRemoteId:[[peripheral identifier] UUIDString]];
[result setType:ProtosBluetoothDevice_Type_Le]; // TODO: Does iOS differentiate?
return result;
}
- (ProtosDeviceStateResponse*)toDeviceStateProto:(CBPeripheral *)peripheral state:(CBPeripheralState)state {
ProtosDeviceStateResponse *result = [[ProtosDeviceStateResponse alloc] init];
switch(state) {
case CBPeripheralStateDisconnected:
[result setState:ProtosDeviceStateResponse_BluetoothDeviceState_Disconnected];
break;
case CBPeripheralStateConnecting:
[result setState:ProtosDeviceStateResponse_BluetoothDeviceState_Connecting];
break;
case CBPeripheralStateConnected:
[result setState:ProtosDeviceStateResponse_BluetoothDeviceState_Connected];
break;
case CBPeripheralStateDisconnecting:
[result setState:ProtosDeviceStateResponse_BluetoothDeviceState_Disconnecting];
break;
}
[result setRemoteId:[[peripheral identifier] UUIDString]];
return result;
}
- (ProtosDiscoverServicesResult*)toServicesResultProto:(CBPeripheral *)peripheral {
ProtosDiscoverServicesResult *result = [[ProtosDiscoverServicesResult alloc] init];
[result setRemoteId:[peripheral.identifier UUIDString]];
NSMutableArray *servicesProtos = [NSMutableArray new];
for(CBService *s in [peripheral services]) {
[servicesProtos addObject:[self toServiceProto:peripheral service:s]];
}
[result setServicesArray:servicesProtos];
return result;
}
- (ProtosBluetoothService*)toServiceProto:(CBPeripheral *)peripheral service:(CBService *)service {
ProtosBluetoothService *result = [[ProtosBluetoothService alloc] init];
NSLog(@"peripheral uuid:%@", [peripheral.identifier UUIDString]);
NSLog(@"service uuid:%@", [service.UUID fullUUIDString]);
[result setRemoteId:[peripheral.identifier UUIDString]];
[result setUuid:[service.UUID fullUUIDString]];
[result setIsPrimary:[service isPrimary]];
// Characteristic Array
NSMutableArray *characteristicProtos = [NSMutableArray new];
for(CBCharacteristic *c in [service characteristics]) {
[characteristicProtos addObject:[self toCharacteristicProto:c]];
}
[result setCharacteristicsArray:characteristicProtos];
// Included Services Array
NSMutableArray *includedServicesProtos = [NSMutableArray new];
for(CBService *s in [service includedServices]) {
[includedServicesProtos addObject:[self toServiceProto:peripheral service:s]];
}
[result setIncludedServicesArray:includedServicesProtos];
return result;
}
- (ProtosBluetoothCharacteristic*)toCharacteristicProto:(CBCharacteristic *)characteristic {
ProtosBluetoothCharacteristic *result = [[ProtosBluetoothCharacteristic alloc] init];
[result setUuid:[characteristic.UUID fullUUIDString]];
[result setProperties:[self toCharacteristicPropsProto:characteristic.properties]];
[result setValue:[characteristic value]];
NSLog(@"uuid: %@ value: %@", [characteristic.UUID fullUUIDString], [characteristic value]);
NSMutableArray *descriptorProtos = [NSMutableArray new];
for(CBDescriptor *d in [characteristic descriptors]) {
[descriptorProtos addObject:[self toDescriptorProto:d]];
}
[result setDescriptorsArray:descriptorProtos];
if([characteristic.service isPrimary]) {
[result setServiceUuid:[characteristic.service.UUID fullUUIDString]];
} else {
// Reverse search to find service and secondary service UUID
[result setSecondaryServiceUuid:[characteristic.service.UUID fullUUIDString]];
CBService *primaryService = [self findPrimaryService:[characteristic service] peripheral:[characteristic.service peripheral]];
[result setServiceUuid:[primaryService.UUID fullUUIDString]];
}
return result;
}
- (ProtosBluetoothDescriptor*)toDescriptorProto:(CBDescriptor *)descriptor {
ProtosBluetoothDescriptor *result = [[ProtosBluetoothDescriptor alloc] init];
[result setUuid:[descriptor.UUID fullUUIDString]];
[result setCharacteristicUuid:[descriptor.characteristic.UUID fullUUIDString]];
[result setServiceUuid:[descriptor.characteristic.service.UUID fullUUIDString]];
int value = [descriptor.value intValue];
[result setValue:[NSData dataWithBytes:&value length:sizeof(value)]];
return result;
}
- (ProtosCharacteristicProperties*)toCharacteristicPropsProto:(CBCharacteristicProperties)props {
ProtosCharacteristicProperties *result = [[ProtosCharacteristicProperties alloc] init];
[result setBroadcast:(props & CBCharacteristicPropertyBroadcast) != 0];
[result setRead:(props & CBCharacteristicPropertyRead) != 0];
[result setWriteWithoutResponse:(props & CBCharacteristicPropertyWriteWithoutResponse) != 0];
[result setWrite:(props & CBCharacteristicPropertyWrite) != 0];
[result setNotify:(props & CBCharacteristicPropertyNotify) != 0];
[result setIndicate:(props & CBCharacteristicPropertyIndicate) != 0];
[result setAuthenticatedSignedWrites:(props & CBCharacteristicPropertyAuthenticatedSignedWrites) != 0];
[result setExtendedProperties:(props & CBCharacteristicPropertyExtendedProperties) != 0];
[result setNotifyEncryptionRequired:(props & CBCharacteristicPropertyNotifyEncryptionRequired) != 0];
[result setIndicateEncryptionRequired:(props & CBCharacteristicPropertyIndicateEncryptionRequired) != 0];
return result;
}
- (void)log:(LogLevel)level format:(NSString *)format, ... {
if(level <= _logLevel) {
va_list args;
va_start(args, format);
// NSString* formattedMessage = [[NSString alloc] initWithFormat:format arguments:args];
NSLog(format, args);
va_end(args);
}
}
@end
@implementation FlutterBleStreamHandler
- (FlutterError*)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)eventSink {
self.sink = eventSink;
return nil;
}
- (FlutterError*)onCancelWithArguments:(id)arguments {
self.sink = nil;
return nil;
}
@end

View File

@@ -0,0 +1,47 @@
#
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
#
Pod::Spec.new do |s|
s.name = 'flutter_ble'
s.version = '0.5.1'
s.summary = 'A new Flutter plugin.'
s.description = <<-DESC
A new Flutter plugin.
DESC
s.homepage = 'http://example.com'
s.license = { :file => '../LICENSE' }
s.author = { 'Your Company' => 'email@example.com' }
s.source = { :path => '.' }
s.source_files = 'Classes/**/*'
s.public_header_files = 'Classes/**/*.h'
s.dependency 'Flutter'
s.framework = 'CoreBluetooth'
s.dependency '!ProtoCompiler'
protoc = ENV['PWD'] + '/Pods/!ProtoCompiler/protoc'
objc_out = 'gen'
proto_in = '../protos'
s.prepare_command = <<-CMD
mkdir -p #{objc_out}
#{protoc} \
--objc_out=#{objc_out} \
--proto_path=#{proto_in} \
#{proto_in}/*.proto
CMD
s.subspec 'Protos' do |ss|
ss.source_files = 'gen/**/*.pbobjc.{h,m}'
ss.header_mappings_dir = 'gen'
ss.requires_arc = false
ss.dependency 'Protobuf'
end
s.pod_target_xcconfig = {
# This is needed by all pods that depend on Protobuf:
'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1',
}
s.ios.deployment_target = '8.0'
end

View File

@@ -0,0 +1,733 @@
// Generated by the protocol buffer compiler. DO NOT EDIT!
// source: flutterblue.proto
// This CPP symbol can be defined to use imports that match up to the framework
// imports needed when using CocoaPods.
#if !defined(GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS)
#define GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS 0
#endif
#if GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS
#import <Protobuf/GPBProtocolBuffers.h>
#else
#import "GPBProtocolBuffers.h"
#endif
#if GOOGLE_PROTOBUF_OBJC_VERSION < 30002
#error This file was generated by a newer version of protoc which is incompatible with your Protocol Buffer library sources.
#endif
#if 30002 < GOOGLE_PROTOBUF_OBJC_MIN_SUPPORTED_VERSION
#error This file was generated by an older version of protoc which is incompatible with your Protocol Buffer library sources.
#endif
// @@protoc_insertion_point(imports)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
CF_EXTERN_C_BEGIN
@class ProtosAdvertisementData;
@class ProtosBluetoothCharacteristic;
@class ProtosBluetoothDescriptor;
@class ProtosBluetoothDevice;
@class ProtosBluetoothService;
@class ProtosCharacteristicProperties;
@class ProtosInt32Value;
@class ProtosReadDescriptorRequest;
@class ProtosWriteCharacteristicRequest;
@class ProtosWriteDescriptorRequest;
NS_ASSUME_NONNULL_BEGIN
#pragma mark - Enum ProtosBluetoothState_State
typedef GPB_ENUM(ProtosBluetoothState_State) {
/**
* Value used if any message's field encounters a value that is not defined
* by this enum. The message will also have C functions to get/set the rawValue
* of the field.
**/
ProtosBluetoothState_State_GPBUnrecognizedEnumeratorValue = kGPBUnrecognizedEnumeratorValue,
ProtosBluetoothState_State_Unknown = 0,
ProtosBluetoothState_State_Unavailable = 1,
ProtosBluetoothState_State_Unauthorized = 2,
ProtosBluetoothState_State_TurningOn = 3,
ProtosBluetoothState_State_On = 4,
ProtosBluetoothState_State_TurningOff = 5,
ProtosBluetoothState_State_Off = 6,
};
GPBEnumDescriptor *ProtosBluetoothState_State_EnumDescriptor(void);
/**
* Checks to see if the given value is defined by the enum or was not known at
* the time this source was generated.
**/
BOOL ProtosBluetoothState_State_IsValidValue(int32_t value);
#pragma mark - Enum ProtosBluetoothDevice_Type
typedef GPB_ENUM(ProtosBluetoothDevice_Type) {
/**
* Value used if any message's field encounters a value that is not defined
* by this enum. The message will also have C functions to get/set the rawValue
* of the field.
**/
ProtosBluetoothDevice_Type_GPBUnrecognizedEnumeratorValue = kGPBUnrecognizedEnumeratorValue,
ProtosBluetoothDevice_Type_Unknown = 0,
ProtosBluetoothDevice_Type_Classic = 1,
ProtosBluetoothDevice_Type_Le = 2,
ProtosBluetoothDevice_Type_Dual = 3,
};
GPBEnumDescriptor *ProtosBluetoothDevice_Type_EnumDescriptor(void);
/**
* Checks to see if the given value is defined by the enum or was not known at
* the time this source was generated.
**/
BOOL ProtosBluetoothDevice_Type_IsValidValue(int32_t value);
#pragma mark - Enum ProtosWriteCharacteristicRequest_WriteType
typedef GPB_ENUM(ProtosWriteCharacteristicRequest_WriteType) {
/**
* Value used if any message's field encounters a value that is not defined
* by this enum. The message will also have C functions to get/set the rawValue
* of the field.
**/
ProtosWriteCharacteristicRequest_WriteType_GPBUnrecognizedEnumeratorValue = kGPBUnrecognizedEnumeratorValue,
ProtosWriteCharacteristicRequest_WriteType_WithResponse = 0,
ProtosWriteCharacteristicRequest_WriteType_WithoutResponse = 1,
};
GPBEnumDescriptor *ProtosWriteCharacteristicRequest_WriteType_EnumDescriptor(void);
/**
* Checks to see if the given value is defined by the enum or was not known at
* the time this source was generated.
**/
BOOL ProtosWriteCharacteristicRequest_WriteType_IsValidValue(int32_t value);
#pragma mark - Enum ProtosDeviceStateResponse_BluetoothDeviceState
typedef GPB_ENUM(ProtosDeviceStateResponse_BluetoothDeviceState) {
/**
* Value used if any message's field encounters a value that is not defined
* by this enum. The message will also have C functions to get/set the rawValue
* of the field.
**/
ProtosDeviceStateResponse_BluetoothDeviceState_GPBUnrecognizedEnumeratorValue = kGPBUnrecognizedEnumeratorValue,
ProtosDeviceStateResponse_BluetoothDeviceState_Disconnected = 0,
ProtosDeviceStateResponse_BluetoothDeviceState_Connecting = 1,
ProtosDeviceStateResponse_BluetoothDeviceState_Connected = 2,
ProtosDeviceStateResponse_BluetoothDeviceState_Disconnecting = 3,
};
GPBEnumDescriptor *ProtosDeviceStateResponse_BluetoothDeviceState_EnumDescriptor(void);
/**
* Checks to see if the given value is defined by the enum or was not known at
* the time this source was generated.
**/
BOOL ProtosDeviceStateResponse_BluetoothDeviceState_IsValidValue(int32_t value);
#pragma mark - ProtosFlutterblueRoot
/**
* Exposes the extension registry for this file.
*
* The base class provides:
* @code
* + (GPBExtensionRegistry *)extensionRegistry;
* @endcode
* which is a @c GPBExtensionRegistry that includes all the extensions defined by
* this file and all files that it depends on.
**/
@interface ProtosFlutterblueRoot : GPBRootObject
@end
#pragma mark - ProtosInt32Value
typedef GPB_ENUM(ProtosInt32Value_FieldNumber) {
ProtosInt32Value_FieldNumber_Value = 1,
};
/**
* Wrapper message for `int32`.
*
* Allows for nullability of fields in messages
**/
@interface ProtosInt32Value : GPBMessage
/** The int32 value. */
@property(nonatomic, readwrite) int32_t value;
@end
#pragma mark - ProtosBluetoothState
typedef GPB_ENUM(ProtosBluetoothState_FieldNumber) {
ProtosBluetoothState_FieldNumber_State = 1,
};
@interface ProtosBluetoothState : GPBMessage
@property(nonatomic, readwrite) ProtosBluetoothState_State state;
@end
/**
* Fetches the raw value of a @c ProtosBluetoothState's @c state property, even
* if the value was not defined by the enum at the time the code was generated.
**/
int32_t ProtosBluetoothState_State_RawValue(ProtosBluetoothState *message);
/**
* Sets the raw value of an @c ProtosBluetoothState's @c state property, allowing
* it to be set to a value that was not defined by the enum at the time the code
* was generated.
**/
void SetProtosBluetoothState_State_RawValue(ProtosBluetoothState *message, int32_t value);
#pragma mark - ProtosAdvertisementData
typedef GPB_ENUM(ProtosAdvertisementData_FieldNumber) {
ProtosAdvertisementData_FieldNumber_LocalName = 1,
ProtosAdvertisementData_FieldNumber_TxPowerLevel = 2,
ProtosAdvertisementData_FieldNumber_Connectable = 3,
ProtosAdvertisementData_FieldNumber_ManufacturerData = 4,
ProtosAdvertisementData_FieldNumber_ServiceData = 5,
ProtosAdvertisementData_FieldNumber_ServiceUuidsArray = 6,
};
@interface ProtosAdvertisementData : GPBMessage
@property(nonatomic, readwrite, copy, null_resettable) NSString *localName;
@property(nonatomic, readwrite, strong, null_resettable) ProtosInt32Value *txPowerLevel;
/** Test to see if @c txPowerLevel has been set. */
@property(nonatomic, readwrite) BOOL hasTxPowerLevel;
@property(nonatomic, readwrite) BOOL connectable;
/** Map of manufacturers to their data */
@property(nonatomic, readwrite, strong, null_resettable) GPBInt32ObjectDictionary<NSData*> *manufacturerData;
/** The number of items in @c manufacturerData without causing the array to be created. */
@property(nonatomic, readonly) NSUInteger manufacturerData_Count;
/** Map of service UUIDs to their data. */
@property(nonatomic, readwrite, strong, null_resettable) NSMutableDictionary<NSString*, NSData*> *serviceData;
/** The number of items in @c serviceData without causing the array to be created. */
@property(nonatomic, readonly) NSUInteger serviceData_Count;
@property(nonatomic, readwrite, strong, null_resettable) NSMutableArray<NSString*> *serviceUuidsArray;
/** The number of items in @c serviceUuidsArray without causing the array to be created. */
@property(nonatomic, readonly) NSUInteger serviceUuidsArray_Count;
@end
#pragma mark - ProtosScanSettings
typedef GPB_ENUM(ProtosScanSettings_FieldNumber) {
ProtosScanSettings_FieldNumber_AndroidScanMode = 1,
ProtosScanSettings_FieldNumber_ServiceUuidsArray = 2,
};
@interface ProtosScanSettings : GPBMessage
@property(nonatomic, readwrite) int32_t androidScanMode;
@property(nonatomic, readwrite, strong, null_resettable) NSMutableArray<NSString*> *serviceUuidsArray;
/** The number of items in @c serviceUuidsArray without causing the array to be created. */
@property(nonatomic, readonly) NSUInteger serviceUuidsArray_Count;
@end
#pragma mark - ProtosScanResult
typedef GPB_ENUM(ProtosScanResult_FieldNumber) {
ProtosScanResult_FieldNumber_Device = 1,
ProtosScanResult_FieldNumber_AdvertisementData = 2,
ProtosScanResult_FieldNumber_Rssi = 3,
};
@interface ProtosScanResult : GPBMessage
/** The received peer's ID. */
@property(nonatomic, readwrite, strong, null_resettable) ProtosBluetoothDevice *device;
/** Test to see if @c device has been set. */
@property(nonatomic, readwrite) BOOL hasDevice;
@property(nonatomic, readwrite, strong, null_resettable) ProtosAdvertisementData *advertisementData;
/** Test to see if @c advertisementData has been set. */
@property(nonatomic, readwrite) BOOL hasAdvertisementData;
@property(nonatomic, readwrite) int32_t rssi;
@end
#pragma mark - ProtosConnectRequest
typedef GPB_ENUM(ProtosConnectRequest_FieldNumber) {
ProtosConnectRequest_FieldNumber_RemoteId = 1,
ProtosConnectRequest_FieldNumber_AndroidAutoConnect = 2,
};
@interface ProtosConnectRequest : GPBMessage
@property(nonatomic, readwrite, copy, null_resettable) NSString *remoteId;
@property(nonatomic, readwrite) BOOL androidAutoConnect;
@end
#pragma mark - ProtosBluetoothDevice
typedef GPB_ENUM(ProtosBluetoothDevice_FieldNumber) {
ProtosBluetoothDevice_FieldNumber_RemoteId = 1,
ProtosBluetoothDevice_FieldNumber_Name = 2,
ProtosBluetoothDevice_FieldNumber_Type = 3,
};
@interface ProtosBluetoothDevice : GPBMessage
@property(nonatomic, readwrite, copy, null_resettable) NSString *remoteId;
@property(nonatomic, readwrite, copy, null_resettable) NSString *name;
@property(nonatomic, readwrite) ProtosBluetoothDevice_Type type;
@end
/**
* Fetches the raw value of a @c ProtosBluetoothDevice's @c type property, even
* if the value was not defined by the enum at the time the code was generated.
**/
int32_t ProtosBluetoothDevice_Type_RawValue(ProtosBluetoothDevice *message);
/**
* Sets the raw value of an @c ProtosBluetoothDevice's @c type property, allowing
* it to be set to a value that was not defined by the enum at the time the code
* was generated.
**/
void SetProtosBluetoothDevice_Type_RawValue(ProtosBluetoothDevice *message, int32_t value);
#pragma mark - ProtosBluetoothService
typedef GPB_ENUM(ProtosBluetoothService_FieldNumber) {
ProtosBluetoothService_FieldNumber_Uuid = 1,
ProtosBluetoothService_FieldNumber_RemoteId = 2,
ProtosBluetoothService_FieldNumber_IsPrimary = 3,
ProtosBluetoothService_FieldNumber_CharacteristicsArray = 4,
ProtosBluetoothService_FieldNumber_IncludedServicesArray = 5,
};
@interface ProtosBluetoothService : GPBMessage
@property(nonatomic, readwrite, copy, null_resettable) NSString *uuid;
@property(nonatomic, readwrite, copy, null_resettable) NSString *remoteId;
/** Indicates whether the type of service is primary or secondary. */
@property(nonatomic, readwrite) BOOL isPrimary;
/** A list of characteristics that have been discovered in this service. */
@property(nonatomic, readwrite, strong, null_resettable) NSMutableArray<ProtosBluetoothCharacteristic*> *characteristicsArray;
/** The number of items in @c characteristicsArray without causing the array to be created. */
@property(nonatomic, readonly) NSUInteger characteristicsArray_Count;
/** A list of included services that have been discovered in this service. */
@property(nonatomic, readwrite, strong, null_resettable) NSMutableArray<ProtosBluetoothService*> *includedServicesArray;
/** The number of items in @c includedServicesArray without causing the array to be created. */
@property(nonatomic, readonly) NSUInteger includedServicesArray_Count;
@end
#pragma mark - ProtosBluetoothCharacteristic
typedef GPB_ENUM(ProtosBluetoothCharacteristic_FieldNumber) {
ProtosBluetoothCharacteristic_FieldNumber_Uuid = 1,
ProtosBluetoothCharacteristic_FieldNumber_ServiceUuid = 2,
ProtosBluetoothCharacteristic_FieldNumber_SecondaryServiceUuid = 3,
ProtosBluetoothCharacteristic_FieldNumber_DescriptorsArray = 4,
ProtosBluetoothCharacteristic_FieldNumber_Properties = 5,
ProtosBluetoothCharacteristic_FieldNumber_Value = 6,
};
@interface ProtosBluetoothCharacteristic : GPBMessage
@property(nonatomic, readwrite, copy, null_resettable) NSString *uuid;
/** The service that this characteristic belongs to. */
@property(nonatomic, readwrite, copy, null_resettable) NSString *serviceUuid;
/** The secondary service if nested */
@property(nonatomic, readwrite, copy, null_resettable) NSString *secondaryServiceUuid;
/** A list of descriptors that have been discovered in this characteristic. */
@property(nonatomic, readwrite, strong, null_resettable) NSMutableArray<ProtosBluetoothDescriptor*> *descriptorsArray;
/** The number of items in @c descriptorsArray without causing the array to be created. */
@property(nonatomic, readonly) NSUInteger descriptorsArray_Count;
/** The properties of the characteristic. */
@property(nonatomic, readwrite, strong, null_resettable) ProtosCharacteristicProperties *properties;
/** Test to see if @c properties has been set. */
@property(nonatomic, readwrite) BOOL hasProperties;
@property(nonatomic, readwrite, copy, null_resettable) NSData *value;
@end
#pragma mark - ProtosBluetoothDescriptor
typedef GPB_ENUM(ProtosBluetoothDescriptor_FieldNumber) {
ProtosBluetoothDescriptor_FieldNumber_Uuid = 1,
ProtosBluetoothDescriptor_FieldNumber_ServiceUuid = 2,
ProtosBluetoothDescriptor_FieldNumber_CharacteristicUuid = 3,
ProtosBluetoothDescriptor_FieldNumber_Value = 4,
};
@interface ProtosBluetoothDescriptor : GPBMessage
@property(nonatomic, readwrite, copy, null_resettable) NSString *uuid;
/** The service that this descriptor belongs to. */
@property(nonatomic, readwrite, copy, null_resettable) NSString *serviceUuid;
/** The characteristic that this descriptor belongs to. */
@property(nonatomic, readwrite, copy, null_resettable) NSString *characteristicUuid;
@property(nonatomic, readwrite, copy, null_resettable) NSData *value;
@end
#pragma mark - ProtosCharacteristicProperties
typedef GPB_ENUM(ProtosCharacteristicProperties_FieldNumber) {
ProtosCharacteristicProperties_FieldNumber_Broadcast = 1,
ProtosCharacteristicProperties_FieldNumber_Read = 2,
ProtosCharacteristicProperties_FieldNumber_WriteWithoutResponse = 3,
ProtosCharacteristicProperties_FieldNumber_Write = 4,
ProtosCharacteristicProperties_FieldNumber_Notify = 5,
ProtosCharacteristicProperties_FieldNumber_Indicate = 6,
ProtosCharacteristicProperties_FieldNumber_AuthenticatedSignedWrites = 7,
ProtosCharacteristicProperties_FieldNumber_ExtendedProperties = 8,
ProtosCharacteristicProperties_FieldNumber_NotifyEncryptionRequired = 9,
ProtosCharacteristicProperties_FieldNumber_IndicateEncryptionRequired = 10,
};
@interface ProtosCharacteristicProperties : GPBMessage
@property(nonatomic, readwrite) BOOL broadcast;
@property(nonatomic, readwrite) BOOL read;
@property(nonatomic, readwrite) BOOL writeWithoutResponse;
@property(nonatomic, readwrite) BOOL write;
@property(nonatomic, readwrite) BOOL notify;
@property(nonatomic, readwrite) BOOL indicate;
@property(nonatomic, readwrite) BOOL authenticatedSignedWrites;
@property(nonatomic, readwrite) BOOL extendedProperties;
@property(nonatomic, readwrite) BOOL notifyEncryptionRequired;
@property(nonatomic, readwrite) BOOL indicateEncryptionRequired;
@end
#pragma mark - ProtosDiscoverServicesResult
typedef GPB_ENUM(ProtosDiscoverServicesResult_FieldNumber) {
ProtosDiscoverServicesResult_FieldNumber_RemoteId = 1,
ProtosDiscoverServicesResult_FieldNumber_ServicesArray = 2,
};
@interface ProtosDiscoverServicesResult : GPBMessage
@property(nonatomic, readwrite, copy, null_resettable) NSString *remoteId;
@property(nonatomic, readwrite, strong, null_resettable) NSMutableArray<ProtosBluetoothService*> *servicesArray;
/** The number of items in @c servicesArray without causing the array to be created. */
@property(nonatomic, readonly) NSUInteger servicesArray_Count;
@end
#pragma mark - ProtosReadCharacteristicRequest
typedef GPB_ENUM(ProtosReadCharacteristicRequest_FieldNumber) {
ProtosReadCharacteristicRequest_FieldNumber_RemoteId = 1,
ProtosReadCharacteristicRequest_FieldNumber_CharacteristicUuid = 2,
ProtosReadCharacteristicRequest_FieldNumber_ServiceUuid = 3,
ProtosReadCharacteristicRequest_FieldNumber_SecondaryServiceUuid = 4,
};
@interface ProtosReadCharacteristicRequest : GPBMessage
@property(nonatomic, readwrite, copy, null_resettable) NSString *remoteId;
@property(nonatomic, readwrite, copy, null_resettable) NSString *characteristicUuid;
@property(nonatomic, readwrite, copy, null_resettable) NSString *serviceUuid;
@property(nonatomic, readwrite, copy, null_resettable) NSString *secondaryServiceUuid;
@end
#pragma mark - ProtosReadCharacteristicResponse
typedef GPB_ENUM(ProtosReadCharacteristicResponse_FieldNumber) {
ProtosReadCharacteristicResponse_FieldNumber_RemoteId = 1,
ProtosReadCharacteristicResponse_FieldNumber_Characteristic = 2,
};
@interface ProtosReadCharacteristicResponse : GPBMessage
@property(nonatomic, readwrite, copy, null_resettable) NSString *remoteId;
@property(nonatomic, readwrite, strong, null_resettable) ProtosBluetoothCharacteristic *characteristic;
/** Test to see if @c characteristic has been set. */
@property(nonatomic, readwrite) BOOL hasCharacteristic;
@end
#pragma mark - ProtosReadDescriptorRequest
typedef GPB_ENUM(ProtosReadDescriptorRequest_FieldNumber) {
ProtosReadDescriptorRequest_FieldNumber_RemoteId = 1,
ProtosReadDescriptorRequest_FieldNumber_DescriptorUuid = 2,
ProtosReadDescriptorRequest_FieldNumber_ServiceUuid = 3,
ProtosReadDescriptorRequest_FieldNumber_SecondaryServiceUuid = 4,
ProtosReadDescriptorRequest_FieldNumber_CharacteristicUuid = 5,
};
@interface ProtosReadDescriptorRequest : GPBMessage
@property(nonatomic, readwrite, copy, null_resettable) NSString *remoteId;
@property(nonatomic, readwrite, copy, null_resettable) NSString *descriptorUuid;
@property(nonatomic, readwrite, copy, null_resettable) NSString *serviceUuid;
@property(nonatomic, readwrite, copy, null_resettable) NSString *secondaryServiceUuid;
@property(nonatomic, readwrite, copy, null_resettable) NSString *characteristicUuid;
@end
#pragma mark - ProtosReadDescriptorResponse
typedef GPB_ENUM(ProtosReadDescriptorResponse_FieldNumber) {
ProtosReadDescriptorResponse_FieldNumber_Request = 1,
ProtosReadDescriptorResponse_FieldNumber_Value = 2,
};
@interface ProtosReadDescriptorResponse : GPBMessage
@property(nonatomic, readwrite, strong, null_resettable) ProtosReadDescriptorRequest *request;
/** Test to see if @c request has been set. */
@property(nonatomic, readwrite) BOOL hasRequest;
@property(nonatomic, readwrite, copy, null_resettable) NSData *value;
@end
#pragma mark - ProtosWriteCharacteristicRequest
typedef GPB_ENUM(ProtosWriteCharacteristicRequest_FieldNumber) {
ProtosWriteCharacteristicRequest_FieldNumber_RemoteId = 1,
ProtosWriteCharacteristicRequest_FieldNumber_CharacteristicUuid = 2,
ProtosWriteCharacteristicRequest_FieldNumber_ServiceUuid = 3,
ProtosWriteCharacteristicRequest_FieldNumber_SecondaryServiceUuid = 4,
ProtosWriteCharacteristicRequest_FieldNumber_WriteType = 5,
ProtosWriteCharacteristicRequest_FieldNumber_Value = 6,
};
@interface ProtosWriteCharacteristicRequest : GPBMessage
@property(nonatomic, readwrite, copy, null_resettable) NSString *remoteId;
@property(nonatomic, readwrite, copy, null_resettable) NSString *characteristicUuid;
@property(nonatomic, readwrite, copy, null_resettable) NSString *serviceUuid;
@property(nonatomic, readwrite, copy, null_resettable) NSString *secondaryServiceUuid;
@property(nonatomic, readwrite) ProtosWriteCharacteristicRequest_WriteType writeType;
@property(nonatomic, readwrite, copy, null_resettable) NSData *value;
@end
/**
* Fetches the raw value of a @c ProtosWriteCharacteristicRequest's @c writeType property, even
* if the value was not defined by the enum at the time the code was generated.
**/
int32_t ProtosWriteCharacteristicRequest_WriteType_RawValue(ProtosWriteCharacteristicRequest *message);
/**
* Sets the raw value of an @c ProtosWriteCharacteristicRequest's @c writeType property, allowing
* it to be set to a value that was not defined by the enum at the time the code
* was generated.
**/
void SetProtosWriteCharacteristicRequest_WriteType_RawValue(ProtosWriteCharacteristicRequest *message, int32_t value);
#pragma mark - ProtosWriteCharacteristicResponse
typedef GPB_ENUM(ProtosWriteCharacteristicResponse_FieldNumber) {
ProtosWriteCharacteristicResponse_FieldNumber_Request = 1,
ProtosWriteCharacteristicResponse_FieldNumber_Success = 2,
};
@interface ProtosWriteCharacteristicResponse : GPBMessage
@property(nonatomic, readwrite, strong, null_resettable) ProtosWriteCharacteristicRequest *request;
/** Test to see if @c request has been set. */
@property(nonatomic, readwrite) BOOL hasRequest;
@property(nonatomic, readwrite) BOOL success;
@end
#pragma mark - ProtosWriteDescriptorRequest
typedef GPB_ENUM(ProtosWriteDescriptorRequest_FieldNumber) {
ProtosWriteDescriptorRequest_FieldNumber_RemoteId = 1,
ProtosWriteDescriptorRequest_FieldNumber_DescriptorUuid = 2,
ProtosWriteDescriptorRequest_FieldNumber_ServiceUuid = 3,
ProtosWriteDescriptorRequest_FieldNumber_SecondaryServiceUuid = 4,
ProtosWriteDescriptorRequest_FieldNumber_CharacteristicUuid = 5,
ProtosWriteDescriptorRequest_FieldNumber_Value = 6,
};
@interface ProtosWriteDescriptorRequest : GPBMessage
@property(nonatomic, readwrite, copy, null_resettable) NSString *remoteId;
@property(nonatomic, readwrite, copy, null_resettable) NSString *descriptorUuid;
@property(nonatomic, readwrite, copy, null_resettable) NSString *serviceUuid;
@property(nonatomic, readwrite, copy, null_resettable) NSString *secondaryServiceUuid;
@property(nonatomic, readwrite, copy, null_resettable) NSString *characteristicUuid;
@property(nonatomic, readwrite, copy, null_resettable) NSData *value;
@end
#pragma mark - ProtosWriteDescriptorResponse
typedef GPB_ENUM(ProtosWriteDescriptorResponse_FieldNumber) {
ProtosWriteDescriptorResponse_FieldNumber_Request = 1,
ProtosWriteDescriptorResponse_FieldNumber_Success = 2,
};
@interface ProtosWriteDescriptorResponse : GPBMessage
@property(nonatomic, readwrite, strong, null_resettable) ProtosWriteDescriptorRequest *request;
/** Test to see if @c request has been set. */
@property(nonatomic, readwrite) BOOL hasRequest;
@property(nonatomic, readwrite) BOOL success;
@end
#pragma mark - ProtosSetNotificationRequest
typedef GPB_ENUM(ProtosSetNotificationRequest_FieldNumber) {
ProtosSetNotificationRequest_FieldNumber_RemoteId = 1,
ProtosSetNotificationRequest_FieldNumber_ServiceUuid = 2,
ProtosSetNotificationRequest_FieldNumber_SecondaryServiceUuid = 3,
ProtosSetNotificationRequest_FieldNumber_CharacteristicUuid = 4,
ProtosSetNotificationRequest_FieldNumber_Enable = 5,
};
@interface ProtosSetNotificationRequest : GPBMessage
@property(nonatomic, readwrite, copy, null_resettable) NSString *remoteId;
@property(nonatomic, readwrite, copy, null_resettable) NSString *serviceUuid;
@property(nonatomic, readwrite, copy, null_resettable) NSString *secondaryServiceUuid;
@property(nonatomic, readwrite, copy, null_resettable) NSString *characteristicUuid;
@property(nonatomic, readwrite) BOOL enable;
@end
#pragma mark - ProtosSetNotificationResponse
typedef GPB_ENUM(ProtosSetNotificationResponse_FieldNumber) {
ProtosSetNotificationResponse_FieldNumber_RemoteId = 1,
ProtosSetNotificationResponse_FieldNumber_Characteristic = 2,
ProtosSetNotificationResponse_FieldNumber_Success = 3,
};
@interface ProtosSetNotificationResponse : GPBMessage
@property(nonatomic, readwrite, copy, null_resettable) NSString *remoteId;
@property(nonatomic, readwrite, strong, null_resettable) ProtosBluetoothCharacteristic *characteristic;
/** Test to see if @c characteristic has been set. */
@property(nonatomic, readwrite) BOOL hasCharacteristic;
@property(nonatomic, readwrite) BOOL success;
@end
#pragma mark - ProtosOnNotificationResponse
typedef GPB_ENUM(ProtosOnNotificationResponse_FieldNumber) {
ProtosOnNotificationResponse_FieldNumber_RemoteId = 1,
ProtosOnNotificationResponse_FieldNumber_Characteristic = 2,
};
@interface ProtosOnNotificationResponse : GPBMessage
@property(nonatomic, readwrite, copy, null_resettable) NSString *remoteId;
@property(nonatomic, readwrite, strong, null_resettable) ProtosBluetoothCharacteristic *characteristic;
/** Test to see if @c characteristic has been set. */
@property(nonatomic, readwrite) BOOL hasCharacteristic;
@end
#pragma mark - ProtosDeviceStateResponse
typedef GPB_ENUM(ProtosDeviceStateResponse_FieldNumber) {
ProtosDeviceStateResponse_FieldNumber_RemoteId = 1,
ProtosDeviceStateResponse_FieldNumber_State = 2,
};
@interface ProtosDeviceStateResponse : GPBMessage
@property(nonatomic, readwrite, copy, null_resettable) NSString *remoteId;
@property(nonatomic, readwrite) ProtosDeviceStateResponse_BluetoothDeviceState state;
@end
/**
* Fetches the raw value of a @c ProtosDeviceStateResponse's @c state property, even
* if the value was not defined by the enum at the time the code was generated.
**/
int32_t ProtosDeviceStateResponse_State_RawValue(ProtosDeviceStateResponse *message);
/**
* Sets the raw value of an @c ProtosDeviceStateResponse's @c state property, allowing
* it to be set to a value that was not defined by the enum at the time the code
* was generated.
**/
void SetProtosDeviceStateResponse_State_RawValue(ProtosDeviceStateResponse *message, int32_t value);
NS_ASSUME_NONNULL_END
CF_EXTERN_C_END
#pragma clang diagnostic pop
// @@protoc_insertion_point(global_scope)

Some files were not shown because too many files have changed in this diff Show More