ESP32 | FLUTTER | BLE - Temperature & Humidity Check App
@@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
Video: https://www.youtube.com/watch?v=oCMOYS71NIU
|
||||||
|
Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp
|
||||||
|
Ported to Arduino ESP32 by Evandro Copercini
|
||||||
|
updated by chegewara
|
||||||
|
|
||||||
|
Create a BLE server that, once we receive a connection, will send periodic notifications.
|
||||||
|
The service advertises itself as: 4fafc201-1fb5-459e-8fcc-c5c9c331914b
|
||||||
|
And has a characteristic of: beb5483e-36e1-4688-b7f5-ea07361b26a8
|
||||||
|
|
||||||
|
The design of creating the BLE server is:
|
||||||
|
1. Create a BLE Server
|
||||||
|
2. Create a BLE Service
|
||||||
|
3. Create a BLE Characteristic on the Service
|
||||||
|
4. Create a BLE Descriptor on the characteristic
|
||||||
|
5. Start the service.
|
||||||
|
6. Start advertising.
|
||||||
|
|
||||||
|
A connect hander associated with the server starts a background task that performs notification
|
||||||
|
every couple of seconds.
|
||||||
|
*/
|
||||||
|
#include <BLEDevice.h>
|
||||||
|
#include <BLEServer.h>
|
||||||
|
#include <BLEUtils.h>
|
||||||
|
#include <BLE2902.h>
|
||||||
|
|
||||||
|
BLEServer* pServer = NULL;
|
||||||
|
BLECharacteristic* pCharacteristic = NULL;
|
||||||
|
bool deviceConnected = false;
|
||||||
|
bool oldDeviceConnected = false;
|
||||||
|
uint32_t value = 0;
|
||||||
|
|
||||||
|
// See the following for generating UUIDs:
|
||||||
|
// https://www.uuidgenerator.net/
|
||||||
|
|
||||||
|
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
|
||||||
|
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
|
||||||
|
|
||||||
|
|
||||||
|
class MyServerCallbacks: public BLEServerCallbacks {
|
||||||
|
void onConnect(BLEServer* pServer) {
|
||||||
|
deviceConnected = true;
|
||||||
|
BLEDevice::startAdvertising();
|
||||||
|
};
|
||||||
|
|
||||||
|
void onDisconnect(BLEServer* pServer) {
|
||||||
|
deviceConnected = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
|
||||||
|
// Create the BLE Device
|
||||||
|
BLEDevice::init("ESP32 Test");
|
||||||
|
|
||||||
|
// Create the BLE Server
|
||||||
|
pServer = BLEDevice::createServer();
|
||||||
|
pServer->setCallbacks(new MyServerCallbacks());
|
||||||
|
|
||||||
|
// Create the BLE Service
|
||||||
|
BLEService *pService = pServer->createService(SERVICE_UUID);
|
||||||
|
|
||||||
|
// Create a BLE Characteristic
|
||||||
|
pCharacteristic = pService->createCharacteristic(
|
||||||
|
CHARACTERISTIC_UUID,
|
||||||
|
BLECharacteristic::PROPERTY_READ |
|
||||||
|
BLECharacteristic::PROPERTY_WRITE |
|
||||||
|
BLECharacteristic::PROPERTY_NOTIFY |
|
||||||
|
BLECharacteristic::PROPERTY_INDICATE
|
||||||
|
);
|
||||||
|
|
||||||
|
// https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
|
||||||
|
// Create a BLE Descriptor
|
||||||
|
pCharacteristic->addDescriptor(new BLE2902());
|
||||||
|
|
||||||
|
// Start the service
|
||||||
|
pService->start();
|
||||||
|
|
||||||
|
// Start advertising
|
||||||
|
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
|
||||||
|
pAdvertising->addServiceUUID(SERVICE_UUID);
|
||||||
|
pAdvertising->setScanResponse(false);
|
||||||
|
pAdvertising->setMinPreferred(0x0); // set value to 0x00 to not advertise this parameter
|
||||||
|
BLEDevice::startAdvertising();
|
||||||
|
Serial.println("Waiting a client connection to notify...");
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
// notify changed value
|
||||||
|
if (deviceConnected) {
|
||||||
|
pCharacteristic->setValue((uint8_t*)&value, 4);
|
||||||
|
pCharacteristic->notify();
|
||||||
|
value++;
|
||||||
|
delay(10); // bluetooth stack will go into congestion, if too many packets are sent, in 6 hours test i was able to go as low as 3ms
|
||||||
|
}
|
||||||
|
// disconnecting
|
||||||
|
if (!deviceConnected && oldDeviceConnected) {
|
||||||
|
delay(500); // give the bluetooth stack the chance to get things ready
|
||||||
|
pServer->startAdvertising(); // restart advertising
|
||||||
|
Serial.println("start advertising");
|
||||||
|
oldDeviceConnected = deviceConnected;
|
||||||
|
}
|
||||||
|
// connecting
|
||||||
|
if (deviceConnected && !oldDeviceConnected) {
|
||||||
|
// do stuff here on connecting
|
||||||
|
oldDeviceConnected = deviceConnected;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,440 @@
|
|||||||
|
// BLE
|
||||||
|
#include <BLEDevice.h>
|
||||||
|
#include <BLEServer.h>
|
||||||
|
#include <BLEUtils.h>
|
||||||
|
#include <BLE2902.h>
|
||||||
|
|
||||||
|
BLEServer* pServer = NULL;
|
||||||
|
BLECharacteristic* pCharacteristic = NULL;
|
||||||
|
bool deviceConnected = false;
|
||||||
|
bool oldDeviceConnected = false;
|
||||||
|
|
||||||
|
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
|
||||||
|
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
|
||||||
|
|
||||||
|
class MyServerCallbacks: public BLEServerCallbacks {
|
||||||
|
void onConnect(BLEServer* pServer) {
|
||||||
|
deviceConnected = true;
|
||||||
|
BLEDevice::startAdvertising();
|
||||||
|
};
|
||||||
|
|
||||||
|
void onDisconnect(BLEServer* pServer) {
|
||||||
|
deviceConnected = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// SCREEN, DHT
|
||||||
|
|
||||||
|
#include "SPIFFS.h"
|
||||||
|
#include <JPEGDecoder.h>
|
||||||
|
|
||||||
|
#include <Adafruit_GFX.h>
|
||||||
|
#include <Adafruit_SSD1351.h>
|
||||||
|
#include <Adafruit_Sensor.h>
|
||||||
|
#include <DHT.h>
|
||||||
|
#include <DHT_U.h>
|
||||||
|
|
||||||
|
#define DHTPIN 19
|
||||||
|
#define DHTTYPE DHT11
|
||||||
|
|
||||||
|
DHT_Unified dht(DHTPIN, DHTTYPE);
|
||||||
|
|
||||||
|
uint32_t delayMS;
|
||||||
|
|
||||||
|
#define SCREEN_WIDTH 128
|
||||||
|
#define SCREEN_HEIGHT 128
|
||||||
|
|
||||||
|
#define SCLK_PIN 18
|
||||||
|
#define MOSI_PIN 23
|
||||||
|
#define DC_PIN 17
|
||||||
|
#define CS_PIN 5
|
||||||
|
#define RST_PIN 4
|
||||||
|
|
||||||
|
// Color definitions
|
||||||
|
#define BLACK 0x0000
|
||||||
|
#define BLUE 0x001F
|
||||||
|
#define RED 0xF800
|
||||||
|
#define GREEN 0x07E0
|
||||||
|
#define CYAN 0x07FF
|
||||||
|
#define MAGENTA 0xF81F
|
||||||
|
#define YELLOW 0xFFE0
|
||||||
|
#define WHITE 0xFFFF
|
||||||
|
|
||||||
|
const String fileTemperature = "/temperature.jpg";
|
||||||
|
const String fileHumidity = "/humidity.jpg";
|
||||||
|
|
||||||
|
Adafruit_SSD1351 tft = Adafruit_SSD1351(SCREEN_WIDTH, SCREEN_HEIGHT, CS_PIN, DC_PIN, MOSI_PIN, SCLK_PIN, RST_PIN);
|
||||||
|
|
||||||
|
float prev_temp;
|
||||||
|
float prev_humidity;
|
||||||
|
|
||||||
|
|
||||||
|
// Thread
|
||||||
|
#include <Thread.h>
|
||||||
|
#include <ThreadController.h>
|
||||||
|
|
||||||
|
// ThreadController that will controll all threads
|
||||||
|
ThreadController controll = ThreadController();
|
||||||
|
|
||||||
|
Thread* btThread = new Thread();
|
||||||
|
Thread* drawingThread = new Thread();
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
// put your setup code here, to run once:
|
||||||
|
Serial.begin(115200);
|
||||||
|
|
||||||
|
initDrawing();
|
||||||
|
initBT();
|
||||||
|
|
||||||
|
btThread->onRun(btCallback);
|
||||||
|
btThread->setInterval(100);
|
||||||
|
|
||||||
|
drawingThread->onRun(drawingCallback);
|
||||||
|
drawingThread->setInterval(delayMS);
|
||||||
|
|
||||||
|
controll.add(btThread);
|
||||||
|
controll.add(drawingThread);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
// put your main code here, to run repeatedly:
|
||||||
|
controll.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawingCallback(){
|
||||||
|
sensors_event_t event;
|
||||||
|
dht.temperature().getEvent(&event);
|
||||||
|
if (isnan(event.temperature)) {
|
||||||
|
Serial.println(F("Error reading temperature!"));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Serial.print(F("Temperature: "));
|
||||||
|
Serial.print(event.temperature);
|
||||||
|
Serial.println(F("°C"));
|
||||||
|
updateTemp(event.temperature);
|
||||||
|
}
|
||||||
|
// Get humidity event and print its value.
|
||||||
|
dht.humidity().getEvent(&event);
|
||||||
|
if (isnan(event.relative_humidity)) {
|
||||||
|
Serial.println(F("Error reading humidity!"));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Serial.print(F("Humidity: "));
|
||||||
|
Serial.print(event.relative_humidity);
|
||||||
|
Serial.println(F("%"));
|
||||||
|
updateHumidity(event.relative_humidity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateTemp(float temp){
|
||||||
|
if(prev_temp != temp){
|
||||||
|
tft.fillRect(64, 0, 128, 64, WHITE);
|
||||||
|
tft.setCursor(70, 24);
|
||||||
|
tft.setTextColor(BLACK);
|
||||||
|
tft.setTextSize(3);
|
||||||
|
String tempString = "";
|
||||||
|
tempString += (int)temp;
|
||||||
|
tempString += "C";
|
||||||
|
tft.print(tempString);
|
||||||
|
prev_temp = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateHumidity(float humidity){
|
||||||
|
if(prev_humidity != humidity){
|
||||||
|
tft.fillRect(64, 64, 128, 128, WHITE);
|
||||||
|
tft.setCursor(70, 88);
|
||||||
|
tft.setTextColor(BLACK);
|
||||||
|
tft.setTextSize(3);
|
||||||
|
String humidityString = "";
|
||||||
|
humidityString += (int)humidity;
|
||||||
|
humidityString += "%";
|
||||||
|
tft.print(humidityString);
|
||||||
|
prev_humidity = humidity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void btCallback(){
|
||||||
|
// notify changed value
|
||||||
|
if (deviceConnected) {
|
||||||
|
|
||||||
|
String str = "";
|
||||||
|
str += prev_temp;
|
||||||
|
str += ",";
|
||||||
|
str += prev_humidity;
|
||||||
|
|
||||||
|
pCharacteristic->setValue((char*)str.c_str());
|
||||||
|
pCharacteristic->notify();
|
||||||
|
|
||||||
|
}
|
||||||
|
// disconnecting
|
||||||
|
if (!deviceConnected && oldDeviceConnected) {
|
||||||
|
//delay(500); // give the bluetooth stack the chance to get things ready
|
||||||
|
pServer->startAdvertising(); // restart advertising
|
||||||
|
Serial.println("start advertising");
|
||||||
|
oldDeviceConnected = deviceConnected;
|
||||||
|
}
|
||||||
|
// connecting
|
||||||
|
if (deviceConnected && !oldDeviceConnected) {
|
||||||
|
// do stuff here on connecting
|
||||||
|
oldDeviceConnected = deviceConnected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void initBT(){
|
||||||
|
// Create the BLE Device
|
||||||
|
BLEDevice::init("ESP32 Test");
|
||||||
|
|
||||||
|
// Create the BLE Server
|
||||||
|
pServer = BLEDevice::createServer();
|
||||||
|
pServer->setCallbacks(new MyServerCallbacks());
|
||||||
|
|
||||||
|
// Create the BLE Service
|
||||||
|
BLEService *pService = pServer->createService(SERVICE_UUID);
|
||||||
|
|
||||||
|
// Create a BLE Characteristic
|
||||||
|
pCharacteristic = pService->createCharacteristic(
|
||||||
|
CHARACTERISTIC_UUID,
|
||||||
|
BLECharacteristic::PROPERTY_READ |
|
||||||
|
BLECharacteristic::PROPERTY_WRITE |
|
||||||
|
BLECharacteristic::PROPERTY_NOTIFY |
|
||||||
|
BLECharacteristic::PROPERTY_INDICATE
|
||||||
|
);
|
||||||
|
|
||||||
|
// https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
|
||||||
|
// Create a BLE Descriptor
|
||||||
|
pCharacteristic->addDescriptor(new BLE2902());
|
||||||
|
|
||||||
|
// Start the service
|
||||||
|
pService->start();
|
||||||
|
|
||||||
|
// Start advertising
|
||||||
|
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
|
||||||
|
pAdvertising->addServiceUUID(SERVICE_UUID);
|
||||||
|
pAdvertising->setScanResponse(false);
|
||||||
|
pAdvertising->setMinPreferred(0x0); // set value to 0x00 to not advertise this parameter
|
||||||
|
BLEDevice::startAdvertising();
|
||||||
|
Serial.println("Waiting a client connection to notify...");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void initDrawing(){
|
||||||
|
tft.begin();
|
||||||
|
tft.fillRect(0,0,128,128,WHITE);
|
||||||
|
|
||||||
|
dht.begin();
|
||||||
|
|
||||||
|
// Print temperature sensor details.
|
||||||
|
sensor_t sensor;
|
||||||
|
dht.temperature().getSensor(&sensor);
|
||||||
|
|
||||||
|
// Print humidity sensor details.
|
||||||
|
dht.humidity().getSensor(&sensor);
|
||||||
|
|
||||||
|
// Set delay between sensor readings based on sensor details.
|
||||||
|
delayMS = sensor.min_delay / 1000;
|
||||||
|
|
||||||
|
if(!SPIFFS.begin()){
|
||||||
|
while(1) yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
drawFSJpeg(fileTemperature.c_str(), 0, 0);
|
||||||
|
drawFSJpeg(fileHumidity.c_str(), 0, 64);
|
||||||
|
|
||||||
|
tft.setCursor(72, 24);
|
||||||
|
tft.setTextColor(BLACK);
|
||||||
|
tft.setTextSize(3);
|
||||||
|
tft.print("?");
|
||||||
|
|
||||||
|
tft.setCursor(72, 88);
|
||||||
|
tft.setTextColor(BLACK);
|
||||||
|
tft.setTextSize(3);
|
||||||
|
tft.print("?");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*====================================================================================
|
||||||
|
This sketch contains support functions to render the Jpeg images.
|
||||||
|
|
||||||
|
Created by Bodmer 15th Jan 2017
|
||||||
|
==================================================================================*/
|
||||||
|
|
||||||
|
// Return the minimum of two values a and b
|
||||||
|
#define minimum(a,b) (((a) < (b)) ? (a) : (b))
|
||||||
|
|
||||||
|
//====================================================================================
|
||||||
|
// This function opens the Filing System Jpeg image file and primes the decoder
|
||||||
|
//====================================================================================
|
||||||
|
void drawFSJpeg(const char *filename, int xpos, int ypos) {
|
||||||
|
|
||||||
|
Serial.println("=====================================");
|
||||||
|
Serial.print("Drawing file: "); Serial.println(filename);
|
||||||
|
Serial.println("=====================================");
|
||||||
|
|
||||||
|
// Open the file (the Jpeg decoder library will close it)
|
||||||
|
fs::File jpgFile = SPIFFS.open( filename, "r"); // File handle reference for SPIFFS
|
||||||
|
// File jpgFile = SD.open( filename, FILE_READ); // or, file handle reference for SD library
|
||||||
|
|
||||||
|
if ( !jpgFile ) {
|
||||||
|
Serial.print("ERROR: File \""); Serial.print(filename); Serial.println ("\" not found!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// To initialise the decoder and provide the file, we can use one of the three following methods:
|
||||||
|
//boolean decoded = JpegDec.decodeFsFile(jpgFile); // We can pass the SPIFFS file handle to the decoder,
|
||||||
|
//boolean decoded = JpegDec.decodeSdFile(jpgFile); // or we can pass the SD file handle to the decoder,
|
||||||
|
boolean decoded = JpegDec.decodeFsFile(filename); // or we can pass the filename (leading / distinguishes SPIFFS files)
|
||||||
|
// The filename can be a String or character array
|
||||||
|
if (decoded) {
|
||||||
|
// print information about the image to the serial port
|
||||||
|
jpegInfo();
|
||||||
|
|
||||||
|
// render the image onto the screen at given coordinates
|
||||||
|
jpegRender(xpos, ypos);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Serial.println("Jpeg file format not supported!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//====================================================================================
|
||||||
|
// Decode and paint onto the TFT screen
|
||||||
|
//====================================================================================
|
||||||
|
void jpegRender(int xpos, int ypos) {
|
||||||
|
|
||||||
|
// retrieve infomration about the image
|
||||||
|
uint16_t *pImg;
|
||||||
|
uint16_t mcu_w = JpegDec.MCUWidth;
|
||||||
|
uint16_t mcu_h = JpegDec.MCUHeight;
|
||||||
|
uint32_t max_x = JpegDec.width;
|
||||||
|
uint32_t max_y = JpegDec.height;
|
||||||
|
|
||||||
|
// Jpeg images are draw as a set of image block (tiles) called Minimum Coding Units (MCUs)
|
||||||
|
// Typically these MCUs are 16x16 pixel blocks
|
||||||
|
// Determine the width and height of the right and bottom edge image blocks
|
||||||
|
uint32_t min_w = minimum(mcu_w, max_x % mcu_w);
|
||||||
|
uint32_t min_h = minimum(mcu_h, max_y % mcu_h);
|
||||||
|
|
||||||
|
// save the current image block size
|
||||||
|
uint32_t win_w = mcu_w;
|
||||||
|
uint32_t win_h = mcu_h;
|
||||||
|
|
||||||
|
// record the current time so we can measure how long it takes to draw an image
|
||||||
|
uint32_t drawTime = millis();
|
||||||
|
|
||||||
|
// save the coordinate of the right and bottom edges to assist image cropping
|
||||||
|
// to the screen size
|
||||||
|
max_x += xpos;
|
||||||
|
max_y += ypos;
|
||||||
|
|
||||||
|
// read each MCU block until there are no more
|
||||||
|
while ( JpegDec.read()) {
|
||||||
|
|
||||||
|
// save a pointer to the image block
|
||||||
|
pImg = JpegDec.pImage;
|
||||||
|
|
||||||
|
// calculate where the image block should be drawn on the screen
|
||||||
|
int mcu_x = JpegDec.MCUx * mcu_w + xpos;
|
||||||
|
int mcu_y = JpegDec.MCUy * mcu_h + ypos;
|
||||||
|
|
||||||
|
// check if the image block size needs to be changed for the right edge
|
||||||
|
if (mcu_x + mcu_w <= max_x) win_w = mcu_w;
|
||||||
|
else win_w = min_w;
|
||||||
|
|
||||||
|
// check if the image block size needs to be changed for the bottom edge
|
||||||
|
if (mcu_y + mcu_h <= max_y) win_h = mcu_h;
|
||||||
|
else win_h = min_h;
|
||||||
|
|
||||||
|
// copy pixels into a contiguous block
|
||||||
|
if (win_w != mcu_w)
|
||||||
|
{
|
||||||
|
for (int h = 1; h < win_h-1; h++)
|
||||||
|
{
|
||||||
|
memcpy(pImg + h * win_w, pImg + (h + 1) * mcu_w, win_w << 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// draw image MCU block only if it will fit on the screen
|
||||||
|
if ( ( mcu_x + win_w) <= tft.width() && ( mcu_y + win_h) <= tft.height())
|
||||||
|
{
|
||||||
|
tft.drawRGBBitmap(mcu_x, mcu_y, pImg, win_w, win_h);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop drawing blocks if the bottom of the screen has been reached,
|
||||||
|
// the abort function will close the file
|
||||||
|
else if ( ( mcu_y + win_h) >= tft.height()) JpegDec.abort();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate how long it took to draw the image
|
||||||
|
drawTime = millis() - drawTime;
|
||||||
|
|
||||||
|
// print the results to the serial port
|
||||||
|
Serial.print ("Total render time was : "); Serial.print(drawTime); Serial.println(" ms");
|
||||||
|
Serial.println("=====================================");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//====================================================================================
|
||||||
|
// Send time taken to Serial port
|
||||||
|
//====================================================================================
|
||||||
|
void jpegInfo() {
|
||||||
|
Serial.println(F("==============="));
|
||||||
|
Serial.println(F("JPEG image info"));
|
||||||
|
Serial.println(F("==============="));
|
||||||
|
Serial.print(F( "Width :")); Serial.println(JpegDec.width);
|
||||||
|
Serial.print(F( "Height :")); Serial.println(JpegDec.height);
|
||||||
|
Serial.print(F( "Components :")); Serial.println(JpegDec.comps);
|
||||||
|
Serial.print(F( "MCU / row :")); Serial.println(JpegDec.MCUSPerRow);
|
||||||
|
Serial.print(F( "MCU / col :")); Serial.println(JpegDec.MCUSPerCol);
|
||||||
|
Serial.print(F( "Scan type :")); Serial.println(JpegDec.scanType);
|
||||||
|
Serial.print(F( "MCU width :")); Serial.println(JpegDec.MCUWidth);
|
||||||
|
Serial.print(F( "MCU height :")); Serial.println(JpegDec.MCUHeight);
|
||||||
|
Serial.println(F("==============="));
|
||||||
|
}
|
||||||
|
|
||||||
|
//====================================================================================
|
||||||
|
// Open a Jpeg file and dump it to the Serial port as a C array
|
||||||
|
//====================================================================================
|
||||||
|
void createArray(const char *filename) {
|
||||||
|
|
||||||
|
fs::File jpgFile; // File handle reference for SPIFFS
|
||||||
|
// File jpgFile; // File handle reference For SD library
|
||||||
|
|
||||||
|
if ( !( jpgFile = SPIFFS.open( filename, "r"))) {
|
||||||
|
Serial.println(F("JPEG file not found"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t data;
|
||||||
|
byte line_len = 0;
|
||||||
|
Serial.println("// Generated by a JPEGDecoder library example sketch:");
|
||||||
|
Serial.println("// https://github.com/Bodmer/JPEGDecoder");
|
||||||
|
Serial.println("");
|
||||||
|
Serial.println("#if defined(__AVR__)");
|
||||||
|
Serial.println(" #include <avr/pgmspace.h>");
|
||||||
|
Serial.println("#endif");
|
||||||
|
Serial.println("");
|
||||||
|
Serial.print("const uint8_t ");
|
||||||
|
while (*filename != '.') Serial.print(*filename++);
|
||||||
|
Serial.println("[] PROGMEM = {"); // PROGMEM added for AVR processors, it is ignored by Due
|
||||||
|
|
||||||
|
while ( jpgFile.available()) {
|
||||||
|
|
||||||
|
data = jpgFile.read();
|
||||||
|
Serial.print("0x"); if (abs(data) < 16) Serial.print("0");
|
||||||
|
Serial.print(data, HEX); Serial.print(",");// Add value and comma
|
||||||
|
line_len++;
|
||||||
|
if ( line_len >= 32) {
|
||||||
|
line_len = 0;
|
||||||
|
Serial.println();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.println("};\r\n");
|
||||||
|
// jpgFile.seek( 0, SeekEnd);
|
||||||
|
jpgFile.close();
|
||||||
|
}
|
||||||
|
//====================================================================================
|
||||||
72
Esp32_temperature_humidity_ble_FLUTTER/flutter_ble_app/.gitignore
vendored
Normal 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
|
||||||
@@ -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
|
||||||
@@ -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.
|
||||||
@@ -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'
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
After Width: | Height: | Size: 544 B |
|
After Width: | Height: | Size: 442 B |
|
After Width: | Height: | Size: 721 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
org.gradle.jvmargs=-Xmx1536M
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
}
|
||||||
7
Esp32_temperature_humidity_ble_FLUTTER/flutter_ble_app/flutter_ble-master/.gitignore
vendored
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
.DS_Store
|
||||||
|
.dart_tool/
|
||||||
|
|
||||||
|
.packages
|
||||||
|
.pub/
|
||||||
|
|
||||||
|
build/
|
||||||
@@ -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
|
||||||
@@ -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.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
TODO: Add your license here.
|
||||||
@@ -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**.
|
||||||
8
Esp32_temperature_humidity_ble_FLUTTER/flutter_ble_app/flutter_ble-master/android/.gitignore
vendored
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
*.iml
|
||||||
|
.gradle
|
||||||
|
/local.properties
|
||||||
|
/.idea/workspace.xml
|
||||||
|
/.idea/libraries
|
||||||
|
.DS_Store
|
||||||
|
/build
|
||||||
|
/captures
|
||||||
@@ -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'
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
org.gradle.jvmargs=-Xmx1536M
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
rootProject.name = 'flutter_ble'
|
||||||
@@ -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>
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
70
Esp32_temperature_humidity_ble_FLUTTER/flutter_ble_app/flutter_ble-master/example/.gitignore
vendored
Executable 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
|
||||||
@@ -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
|
||||||
@@ -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.
|
||||||
@@ -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'
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
After Width: | Height: | Size: 544 B |
|
After Width: | Height: | Size: 442 B |
|
After Width: | Height: | Size: 721 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
org.gradle.jvmargs=-Xmx1536M
|
||||||
|
android.useAndroidX=true
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||||
|
#include "Generated.xcconfig"
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||||
|
#include "Generated.xcconfig"
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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 */;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "group:Runner.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
#import <Flutter/Flutter.h>
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
@interface AppDelegate : FlutterAppDelegate
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -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
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 564 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 68 B |
|
After Width: | Height: | Size: 68 B |
|
After Width: | Height: | Size: 68 B |
@@ -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.
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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]));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
@@ -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
|
||||||
@@ -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,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
36
Esp32_temperature_humidity_ble_FLUTTER/flutter_ble_app/flutter_ble-master/ios/.gitignore
vendored
Executable 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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||