Приветствую вас, уважаемые датагорцы!
Сегодня — обмен данными между Android-приложением и nRF52832 посредством низко-энергетического Bluetooth.
Содержание статьи / Table Of Contents
↑ Видео-демонстрация проекта
↑ Код для nRF52832
Программа для nRF52832 написана на языке Си в Visual Studio Code и обеспечивает следующие функции:1. Измерение температуры посредством датчика DS18B20.
2. До соединения Android, выступающего в качестве центрального устройства — широковещание (advertising) с передачей в эфир следующих GAP-данных:
• адрес устройства,
• тип устройства (LE only),
• флаги (General Discoverable Mode, BR/EDR Not Supported),
• UUID и имя сервиса.
3. После соединения с центральным устройством — обмен с последним GATT-данными, в т.ч.:
• уведомления (notifications) о текущем значении температуры,
• изменение значения характеристики (состояния светодиода) при поступлении соответствующей команды от Android.
↑ Контроль за светодиодом
Управление светодиодом, подключённого к пину P0.17, осуществляется через одноимённую библиотеку:led.h
#ifndef LED_H_
#define LED_H_
#include "nrf.h"
#define LED 17
#define LED_OFF NRF_GPIO->OUT |= (1UL << LED)
#define LED_ON NRF_GPIO->OUT &= ~(1UL << LED)
void ledInit();
#endif
led.c
#include "led.h"
void ledInit()
{
NRF_GPIO->PIN_CNF[LED] |= (GPIO_PIN_CNF_DIR_Output << GPIO_PIN_CNF_DIR_Pos);
LED_OFF;
}
↑ Контроль за DS18B20
В подробно описанный в в моей статье "Электронные часы-термометр с беспроводным датчиком через радиомодуль nRF24L01" код управления температурным датчиком внесено лишь одно изменение: измерение и чтение значения температуры выделены в отдельные функции (DS18B20StartConversion и DS18B20ReadTemperature), что обусловлено таймингом BLE-соединения:oneWire.h
#ifndef _OWI_H_
#define _OWI_H_
#include "nrf.h"
#include "nrf_delay.h"
#define DELAY_A 6
#define DELAY_B 64
#define DELAY_C 60
#define DELAY_D 10
#define DELAY_E 9
#define DELAY_F 55
#define DELAY_G 0
#define DELAY_H 480
#define DELAY_I 70
#define DELAY_J 410
#define MSBit 0x80
#define LSBit 0x01
#define OWI_BUS 4
#define DIR 0
#define PULL 2
#define OWI_RELEASE_BUS NRF_GPIO->PIN_CNF[OWI_BUS] = 0
#define OWI_PULL_BUS_LOW NRF_GPIO->PIN_CNF[OWI_BUS] = 1 << DIR
void owiInit(void);
void owiReset(void);
void owiWriteBit0(void);
void owiWriteBit1(void);
unsigned char owiReadBit(void);
void owiSendByte(unsigned char data);
unsigned char owiReceiveByte(void);
#endif
oneWire.c
#include "oneWire.h"
void owiInit(void)
{
OWI_RELEASE_BUS;
nrf_delay_us(DELAY_H);
}
void owiReset(void)
{
OWI_PULL_BUS_LOW;
nrf_delay_us(DELAY_H);
OWI_RELEASE_BUS;
nrf_delay_us(DELAY_I);
nrf_delay_us(DELAY_J);
}
void owiWriteBit0(void)
{
OWI_PULL_BUS_LOW;
nrf_delay_us(DELAY_C);
OWI_RELEASE_BUS;
nrf_delay_us(DELAY_D);
}
void owiWriteBit1(void)
{
OWI_PULL_BUS_LOW;
nrf_delay_us(DELAY_A);
OWI_RELEASE_BUS;
nrf_delay_us(DELAY_B);
}
unsigned char owiReadBit(void)
{
uint32_t bitsRead = 0;
OWI_PULL_BUS_LOW;
nrf_delay_us(DELAY_A);
OWI_RELEASE_BUS;
nrf_delay_us(DELAY_E);
if(NRF_GPIO->IN & (1 << OWI_BUS))
bitsRead = 1;
nrf_delay_us(DELAY_F);
return bitsRead;
}
void owiSendByte(unsigned char data)
{
unsigned char temp, currentBit;
for (currentBit = 0; currentBit < 8; currentBit++)
{
temp = data & 0x01;
if (temp)
{
owiWriteBit1();
}
else
{
owiWriteBit0();
}
data >>= 1;
}
}
unsigned char owiReceiveByte(void)
{
unsigned char data = 0, currentBit = 0;
for (currentBit = 0; currentBit < 8; currentBit++)
{
data >>= 1;
if (owiReadBit())
{
data |= MSBit;
}
}
return data;
}
DS18B20.h
#ifndef _DS18B20_H_
#define _DS18B20_H_
#include <stdint.h>
#include "oneWire.h"
#define SKIP_ROM 0xCC
#define READ_SCRATCH_PAD 0xBE
#define CONVERT_T 0x44
#define MINUS 1
#define PLUS 0
#define FREE_STATE 0
#define CONVERSION_STATE 1
void DS18B20Init();
void DS18B20StartConversion() ;
void DS18B20ReadTemperature();
#endif
DS18B20.c
#include "DS18B20.h"
uint8_t temperature[4] = {0}, DS18B20_state = FREE_STATE;
void DS18B20Init()
{
owiInit();
}
void DS18B20StartConversion()
{
DS18B20_state = CONVERSION_STATE;
owiReset();
owiSendByte(SKIP_ROM);
owiSendByte(CONVERT_T);
}
void DS18B20ReadTemperature(void)
{
unsigned char ds18b20_data[2];
owiReset();
owiSendByte(SKIP_ROM);
owiSendByte(READ_SCRATCH_PAD);
for(int i = 0; i < 2; i++)
{
ds18b20_data[i] = owiReceiveByte();
}
owiReset();
float currentTemperature = (ds18b20_data[1] << 8 | ds18b20_data[0])/16.0;
if(currentTemperature >= 0)
{
temperature[0] = PLUS;
}
else if(currentTemperature < 0)
{
temperature[0] = MINUS;
}
temperature[1] = (uint8_t)(currentTemperature / 10);
temperature[2] = (uint8_t)(currentTemperature - temperature[1] * 10);
temperature[3] = (uint8_t)(currentTemperature * 10 - temperature[1] * 100 - temperature[2] * 10);
DS18B20_state = FREE_STATE;
}
↑ Контроль за BLE
За основу принят шаблон ble_app_template из SDK 17.0.2.Далее, добавлена библиотека myService, которая обеспечивает работу сервиса посредством функций, имеющими следующее назначение:
• myServiceInit — инициализация сервиса.
• on_connect и on_disconnect — соединение и разъединение с центральным устройством, соответственно.
• on_write — изменение состояния светодиода и разрешение уведомлений при получении соответствующих данных от Android.
• custom_value_char_add — добавление характеристики в сервис.
• ble_cus_custom_value_update — обновление значения температуры в уведомлении.
• ble_cus_on_ble_evt — обработка событий BLE.
myService.h
#ifndef MY_SERVICE_
#define MY_SERVICE_
#include <stdint.h>
#include <stdbool.h>
#include "ble.h"
#include "ble_srv_common.h"
#include "led.h"
#define CUSTOM_SERVICE_UUID_BASE {0xBC, 0x8A, 0xBF, 0x45, 0xCA, 0x05, 0x50, 0xBA, \
0x40, 0x42, 0xB0, 0x00, 0xC9, 0xAD, 0x64, 0xF3}
#define MY_SERVICE_UUID 0x1400
#define MY_VALUE_CHAR_UUID 0x1401
#define BLE_CUS_DEF(_name) \
static ble_cus_t _name; \
NRF_SDH_BLE_OBSERVER(_name ## _obs, \
BLE_HRS_BLE_OBSERVER_PRIO, \
ble_cus_on_ble_evt, &_name)
typedef enum
{
BLE_CUS_EVT_NOTIFICATION_ENABLED,
BLE_CUS_EVT_NOTIFICATION_DISABLED,
BLE_CUS_EVT_DISCONNECTED,
BLE_CUS_EVT_CONNECTED
} ble_cus_evt_type_t;
typedef struct
{
ble_cus_evt_type_t evt_type;
} ble_cus_evt_t;
typedef struct ble_cus_s ble_cus_t;
typedef void (*ble_cus_evt_handler_t) (ble_cus_t * p_bas, ble_cus_evt_t * p_evt);
typedef struct
{
ble_cus_evt_handler_t evt_handler;
uint8_t initial_custom_value;
ble_srv_cccd_security_mode_t custom_value_char_attr_md;
} ble_cus_init_t;
struct ble_cus_s
{
ble_cus_evt_handler_t evt_handler;
uint16_t service_handle;
ble_gatts_char_handles_t custom_value_handles;
uint16_t conn_handle;
uint8_t uuid_type;
};
uint32_t myServiceInit(ble_cus_t * p_cus, const ble_cus_init_t * p_cus_init);
void ble_cus_on_ble_evt( ble_evt_t const * p_ble_evt, void * p_context);
uint32_t ble_cus_custom_value_update(ble_cus_t * p_cus, uint8_t temperature[4]);
#endif
myService.c
#include "sdk_common.h"
#include "ble_srv_common.h"
#include "myService.h"
#include <string.h>
#include "nrf_log.h"
static void on_connect(ble_cus_t * p_cus, ble_evt_t const * p_ble_evt)
{
p_cus->conn_handle = p_ble_evt->evt.gap_evt.conn_handle;
ble_cus_evt_t evt;
evt.evt_type = BLE_CUS_EVT_CONNECTED;
p_cus->evt_handler(p_cus, &evt);
}
static void on_disconnect(ble_cus_t * p_cus, ble_evt_t const * p_ble_evt)
{
UNUSED_PARAMETER(p_ble_evt);
p_cus->conn_handle = BLE_CONN_HANDLE_INVALID;
}
static void on_write(ble_cus_t * p_cus, ble_evt_t const * p_ble_evt)
{
ble_gatts_evt_write_t const * p_evt_write = &p_ble_evt->evt.gatts_evt.params.write;
if (p_evt_write->handle == p_cus->custom_value_handles.value_handle)
{
if(p_evt_write->data[0] == 1)
{
NRF_LOG_INFO("Led ON");
LED_ON;
}
else if(p_evt_write->data[0] == 2)
{
NRF_LOG_INFO("Led OFF");
LED_OFF;
}
}
if ((p_evt_write->handle == p_cus->custom_value_handles.cccd_handle) && (p_evt_write->len == 2))
{
if (p_cus->evt_handler != NULL)
{
ble_cus_evt_t evt;
if (ble_srv_is_notification_enabled(p_evt_write->data))
{
evt.evt_type = BLE_CUS_EVT_NOTIFICATION_ENABLED;
}
else
{
evt.evt_type = BLE_CUS_EVT_NOTIFICATION_DISABLED;
}
p_cus->evt_handler(p_cus, &evt);
}
}
}
static uint32_t custom_value_char_add(ble_cus_t * p_cus, const ble_cus_init_t * p_cus_init)
{
uint32_t err_code;
ble_gatts_char_md_t char_md;
ble_gatts_attr_md_t cccd_md;
ble_gatts_attr_t attr_char_value;
ble_uuid_t ble_uuid;
ble_gatts_attr_md_t attr_md;
ble_uuid.type = p_cus->uuid_type;
ble_uuid.uuid = MY_SERVICE_UUID;
memset(&char_md, 0, sizeof(char_md));
char_md.char_props.read = 1;
char_md.char_props.write = 1;
char_md.char_props.notify = 1;
char_md.p_char_user_desc = NULL;
char_md.p_char_pf = NULL;
char_md.p_user_desc_md = NULL;
char_md.p_cccd_md = &cccd_md;
char_md.p_sccd_md = NULL;
memset(&attr_md, 0, sizeof(attr_md));
attr_md.read_perm = p_cus_init->custom_value_char_attr_md.read_perm;
attr_md.write_perm = p_cus_init->custom_value_char_attr_md.write_perm;
attr_md.vloc = BLE_GATTS_VLOC_STACK;
attr_md.rd_auth = 0;
attr_md.wr_auth = 0;
attr_md.vlen = 0;
memset(&attr_char_value, 0, sizeof(attr_char_value));
attr_char_value.p_uuid = &ble_uuid;
attr_char_value.p_attr_md = &attr_md;
attr_char_value.init_len = 4;
attr_char_value.init_offs = 0;
attr_char_value.max_len = 4;
memset(&cccd_md, 0, sizeof(cccd_md));
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.read_perm);
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.write_perm);
cccd_md.vloc = BLE_GATTS_VLOC_STACK;
err_code = sd_ble_gatts_characteristic_add(p_cus->service_handle, &char_md, &attr_char_value, &p_cus->custom_value_handles);
if (err_code != NRF_SUCCESS)
{
return err_code;
}
return NRF_SUCCESS;
}
uint32_t ble_cus_custom_value_update(ble_cus_t * p_cus, uint8_t temperature[4])
{
if (p_cus == NULL)
{
return NRF_ERROR_NULL;
}
uint32_t err_code = NRF_SUCCESS;
ble_gatts_value_t gatts_value;
memset(&gatts_value, 0, sizeof(gatts_value));
gatts_value.len = 4;
gatts_value.offset = 0;
gatts_value.p_value = &temperature[0];
err_code = sd_ble_gatts_value_set(p_cus->conn_handle, p_cus->custom_value_handles.value_handle, &gatts_value);
if (err_code != NRF_SUCCESS)
{
return err_code;
}
if ((p_cus->conn_handle != BLE_CONN_HANDLE_INVALID))
{
ble_gatts_hvx_params_t hvx_params;
memset(&hvx_params, 0, sizeof(hvx_params));
hvx_params.handle = p_cus->custom_value_handles.value_handle;
hvx_params.type = BLE_GATT_HVX_NOTIFICATION;
hvx_params.offset = gatts_value.offset;
hvx_params.p_len = &gatts_value.len;
hvx_params.p_data = gatts_value.p_value;
err_code = sd_ble_gatts_hvx(p_cus->conn_handle, &hvx_params);
}
else
{
err_code = NRF_ERROR_INVALID_STATE;
}
return err_code;
}
uint32_t myServiceInit(ble_cus_t * p_cus, const ble_cus_init_t * p_cus_init)
{
if (p_cus == NULL || p_cus_init == NULL)
{
return NRF_ERROR_NULL;
}
uint32_t err_code;
ble_uuid_t ble_uuid;
p_cus->evt_handler = p_cus_init->evt_handler;
p_cus->conn_handle = BLE_CONN_HANDLE_INVALID;
ble_uuid128_t base_uuid = {custom_SERVICE_UUID_BASE};
err_code = sd_ble_uuid_vs_add(&base_uuid, &p_cus->uuid_type);
VERIFY_SUCCESS(err_code);
ble_uuid.type = p_cus->uuid_type;
ble_uuid.uuid = MY_SERVICE_UUID;
err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, &ble_uuid, &p_cus->service_handle);
if (err_code != NRF_SUCCESS)
{
return err_code;
}
return custom_value_char_add(p_cus, p_cus_init);
}
void ble_cus_on_ble_evt( ble_evt_t const * p_ble_evt, void * p_context)
{
ble_cus_t * p_cus = (ble_cus_t *) p_context;
if (p_cus == NULL || p_ble_evt == NULL)
{
return;
}
switch (p_ble_evt->header.evt_id)
{
case BLE_GAP_EVT_CONNECTED:
on_connect(p_cus, p_ble_evt);
break;
case BLE_GAP_EVT_DISCONNECTED:
on_disconnect(p_cus, p_ble_evt);
break;
case BLE_GATTS_EVT_WRITE:
on_write(p_cus, p_ble_evt);
break;
default:
break;
}
}
Кроме того, в main.c добавлены следующие фрагменты:
1. Функция notification_timeout_handler измеряет температуру и обновляет её значение в уведомлении.
static void notification_timeout_handler(void * p_context)
{
UNUSED_PARAMETER(p_context);
ret_code_t err_code;
if(DS18B20_state == FREE_STATE)
{
DS18B20StartConversion();
}
else if(DS18B20_state == CONVERSION_STATE)
{
DS18B20ReadTemperature();
NRF_LOG_INFO("%d%d.%d", temperature[1], temperature[2], temperature[3]);
err_code = ble_cus_custom_value_update(&m_cus, &temperature[0]);
APP_ERROR_CHECK(err_code);
}
}
2. Функция timers_init настраивает один из таймеров, по прерыванию которого каждые 750 мс процессор переходит в к предыдущей функции.
static void timers_init(void)
{
ret_code_t err_code = app_timer_init();
APP_ERROR_CHECK(err_code);
err_code = app_timer_create(&m_notification_timer_id, APP_TIMER_MODE_REPEATED, notification_timeout_handler);
APP_ERROR_CHECK(err_code);
}
3. Обработчик on_cus_evt включает/выключает упомянутый выше таймер при поступлении от Android разрешения/запрета на уведомления, соответственно.
static void on_cus_evt(ble_cus_t * p_cus_service, ble_cus_evt_t * p_evt)
{
ret_code_t err_code;
switch(p_evt->evt_type)
{
case BLE_CUS_EVT_NOTIFICATION_ENABLED:
err_code = app_timer_start(m_notification_timer_id, NOTIFICATION_INTERVAL, NULL);
APP_ERROR_CHECK(err_code);
break;
case BLE_CUS_EVT_NOTIFICATION_DISABLED:
err_code = app_timer_stop(m_notification_timer_id);
APP_ERROR_CHECK(err_code);
break;
case BLE_CUS_EVT_CONNECTED:
break;
case BLE_CUS_EVT_DISCONNECTED:
break;
default:
break;
}
}
4. Функция services_init инициализирует сервис и определяет on_cus_evt в качестве обработчика BLE-событий.
static void services_init(void)
{
ret_code_t err_code;
nrf_ble_qwr_init_t qwr_init = {0};
ble_cus_init_t cus_init = {0};
qwr_init.error_handler = nrf_qwr_error_handler;
err_code = nrf_ble_qwr_init(&m_qwr, &qwr_init);
APP_ERROR_CHECK(err_code);
cus_init.evt_handler = on_cus_evt;
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cus_init.custom_value_char_attr_md.cccd_write_perm);
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cus_init.custom_value_char_attr_md.read_perm);
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cus_init.custom_value_char_attr_md.write_perm);
err_code = myServiceInit(&m_cus, &cus_init);
APP_ERROR_CHECK(err_code);
}
В завершение, внесены соответствующие дополнения в основную функцию.
int main(void)
{
ledInit();
DS18B20Init();
log_init();
timers_init();
power_management_init();
ble_stack_init();
gap_params_init();
gatt_init();
services_init();
advertising_init();
conn_params_init();
peer_manager_init();
advertising_start(true);
while(1)
{
idle_state_handle();
}
}
Код проекта для nRF52832 выложен в архив статьи.
↑ Android-приложение
Приложение написано на языке Java в Android Studio.В манифесте прописаны разрешения на работу с Bluetooth.
AndroidManifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.Ds18b20"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
В xml-файле определены параметры:
• кнопки сканирования BLE-устройств,
• кнопок соединения/разъединения с заданным периферийным устройством,
• кнопок включения/выключения светодиода, входящего в состав nRF52832,
• текстового поля для отражения значения температуры.
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/scanBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="scan"
android:text="scan"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.049"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.046" />
<Button
android:id="@+id/connectBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="connect"
android:text="connect"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.046" />
<Button
android:id="@+id/disconnectBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="disconnect"
android:text="disconnect"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.046" />
<Button
android:id="@+id/ledOnbtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="ledOn"
android:text="on"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.95"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.046" />
<Button
android:id="@+id/ledOffbtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="ledOff"
android:text="off"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.949"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.046" />
<TextView
android:id="@+id/textView"
android:layout_width="402dp"
android:layout_height="146dp"
android:gravity="center"
android:text="+00.0℃"
android:textSize="50sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.444"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.256" />
</androidx.constraintlayout.widget.ConstraintLayout>
Код Java-файла по нажатию соответствующей кнопки:
а) Осуществляет сканирование и, в случае обнаружения BLE-устройства, выдаёт сообщение с указанием его адреса.
б) Производит соединение с заданным устройством и разрешает уведомления от последнего.
в) Передаёт команду на запись значения характеристики, ответственного за состояние светодиода.
Кроме того, по получению уведомления от nRF52832 обновляется значение температуры в текстовом поле.
MainActivity.java
package com.punchthrough.ds18b20;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.app.Service;
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.BluetoothGattServer;
import android.bluetooth.BluetoothGattService;
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.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Trace;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
public class MainActivity extends AppCompatActivity {
private String TAG = "MainActivity";
public final static String ACTION_DATA_AVAILABLE =
"com.example.bluetooth.le.ACTION_DATA_AVAILABLE";
public static final UUID SERVICE_UUID = UUID.fromString("f3641400-00b0-4240-ba50-05ca45bf8abc");
public static final UUID CHARACTERISTIC_UUID = UUID.fromString("f3641400-00b0-4240-ba50-05ca45bf8abc");
public static final UUID DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
BluetoothLeScanner scanner = adapter.getBluetoothLeScanner();
BluetoothDevice device;
BluetoothGatt bluetoothGatt;
Button scanButton, connectBtn, disconnectBtn, ledOnBtn, ledOffBtn;
TextView textView;
String[] names = new String[]{"myService"};
List<ScanFilter> filters = null;
BluetoothGattCharacteristic gattCharacteristic;
BluetoothGattDescriptor descriptor;
private final static int REQUEST_ENABLE_BT = 1;
private static final int PERMISSION_REQUEST_COARSE_LOCATION = 1;
ScanSettings scanSettings = new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
.setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
.setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT)
.setReportDelay(0L)
.build();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
scanButton = findViewById(R.id.scanBtn);
connectBtn = findViewById(R.id.connectBtn);
connectBtn.setEnabled(false);
disconnectBtn = findViewById(R.id.disconnectBtn);
disconnectBtn.setEnabled(false);
disconnectBtn.setVisibility(View.INVISIBLE);
ledOnBtn = findViewById(R.id.ledOnbtn);
ledOnBtn.setEnabled(false);
ledOffBtn = findViewById(R.id.ledOffbtn);
ledOffBtn.setVisibility(View.INVISIBLE);
textView = findViewById(R.id.textView);
if (names != null) {
filters = new ArrayList<>();
for (String name : names) {
ScanFilter filter = new ScanFilter.Builder()
.setDeviceName(name)
.build();
filters.add(filter);
}
}
if (adapter != null && !adapter.isEnabled()) {
if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1);
}
Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
}
if (this.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("This app needs location access");
builder.setMessage("Please grant location access so this app can detect peripherals.");
builder.setPositiveButton(android.R.string.ok, null);
builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, PERMISSION_REQUEST_COARSE_LOCATION);
}
});
builder.show();
}
}
public void scan(View view) {
if (scanner != null) {
if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1);
}
scanner.startScan(filters, scanSettings, scanCallback);
Toast.makeText(this, "scanning...", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(this, "could not get scanner object", Toast.LENGTH_LONG).show();
}
}
public void connect(View view) {
Toast.makeText(this, "connecting...", Toast.LENGTH_LONG).show();
if (scanner != null) {
if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1);
}
bluetoothGatt = device.connectGatt(MainActivity.this, false, btleGattCallback);
ledOnBtn.setEnabled(true);
ledOffBtn.setEnabled(true);
} else {
Toast.makeText(this, "could not connect", Toast.LENGTH_LONG).show();
}
}
public void disconnect(View view) {
if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1);
}
bluetoothGatt.disconnect();
ledOnBtn.setEnabled(false);
ledOffBtn.setEnabled(false);
connectBtn.setEnabled(false);
}
private final ScanCallback scanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
device = result.getDevice();
Toast.makeText(MainActivity.this, "device with address" + "\n" + device.getAddress() + "\n" + "found", Toast.LENGTH_LONG).show();
if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1);
}
scanner.stopScan(scanCallback);
connectBtn.setEnabled(true);
disconnectBtn.setEnabled(true);
ledOnBtn.setEnabled(true);
}
};
private final BluetoothGattCallback btleGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(final BluetoothGatt gatt, final int status, final int newState) {
System.out.println(newState);
switch (newState) {
case 0:
MainActivity.this.runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(MainActivity.this, "device disconnected", Toast.LENGTH_SHORT).show();
connectBtn.setVisibility(View.VISIBLE);
disconnectBtn.setVisibility(View.INVISIBLE);
}
});
break;
case 2:
MainActivity.this.runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(MainActivity.this, "device connected", Toast.LENGTH_SHORT).show();
connectBtn.setVisibility(View.INVISIBLE);
disconnectBtn.setVisibility(View.VISIBLE);
}
});
if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1);
}
bluetoothGatt.discoverServices();
break;
default:
MainActivity.this.runOnUiThread(new Runnable() {
public void run() {
Log.i(TAG, "we encounterned an unknown state, uh oh\n");
}
});
break;
}
}
@Override
public void onServicesDiscovered(final BluetoothGatt gatt, final int status) {
MainActivity.this.runOnUiThread(new Runnable() {
public void run() {
Log.i(TAG, "device services have been discovered\n");
}
});
if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1);
}
gattCharacteristic = gatt.getService(SERVICE_UUID).getCharacteristic(CHARACTERISTIC_UUID);
gatt.setCharacteristicNotification(gattCharacteristic, true);
descriptor = gattCharacteristic.getDescriptor(DESCRIPTOR_UUID);
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
gatt.writeDescriptor(descriptor);
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
byte[] data = characteristic.getValue();
MainActivity.this.runOnUiThread(new Runnable() {
@SuppressLint("SetTextI18n")
public void run() {
if ((data[0] == 0)) {
textView.setText("+" + data[1] + data[2] + "." + data[3] + "\u2103");
}
if ((data[0] == 1)) {
textView.setText("-" + + data[1] + data[2] + "." + data[3] + "\u2103");
}
}
});
}
};
public void ledOn(View view) {
byte[] value = new byte[1];
value[0] = (byte) (0x01);
BluetoothGattService mCustomService = bluetoothGatt.getService(UUID.fromString("f3641400-00b0-4240-ba50-05ca45bf8abc"));
if(mCustomService == null){
Log.i(TAG, "Custom BLE Service not found");
return;
}
BluetoothGattCharacteristic characteristic = mCustomService.getCharacteristic(UUID.fromString("f3641400-00b0-4240-ba50-05ca45bf8abc"));
characteristic.setValue(value);
if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1);
}
bluetoothGatt.writeCharacteristic(characteristic);
ledOnBtn.setVisibility(View.INVISIBLE);
ledOffBtn.setVisibility(View.VISIBLE);
}
public void ledOff(View view) {
byte[] value = new byte[1];
value[0] = (byte) (0x02);
BluetoothGattService mCustomService = bluetoothGatt.getService(UUID.fromString("f3641400-00b0-4240-ba50-05ca45bf8abc"));
if(mCustomService == null){
Log.i(TAG, "Custom BLE Service not found");
return;
}
BluetoothGattCharacteristic characteristic = mCustomService.getCharacteristic(UUID.fromString("f3641400-00b0-4240-ba50-05ca45bf8abc"));
characteristic.setValue(value);
if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1);
}
bluetoothGatt.writeCharacteristic(characteristic);
ledOnBtn.setVisibility(View.VISIBLE);
ledOffBtn.setVisibility(View.INVISIBLE);
}
}
Спасибо за внимание!
↑ Файлы
🎁android.zip 10.64 Mb ⇣ 19🎁nrf52832.zip 50.36 Kb ⇣ 17