[Разработка под iOS, Разработка мобильных приложений, Swift] Подключаем нагрудный датчик пульса по Bluetooth на Swift
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
С чего все началось?
Около года назад я приобрел данный девайс для контроля частоты сердечных сокращений (далее - ЧСС) во время тренировок. Датчик отлично подсоединяется к телефону, умным часам по Bluetooth, но обычно, фитнесс-приложения, анализирующие подобного рода данные требуют либо подписки, либо нагружены излишне сложными аналитиками, которые мне, как рядовому пользователю не очень интересны. Поэтому у меня родилась идея написать свое приложение для контроля ЧСС во время тренировок для IOS на Swift.Немного теории о технологии Bluetooth LEBluetooth Low Energy - очень популярный и распространённый протокол обмена данными, который мы используем повсеместно и который становится все популярнее с каждым днем. У меня даже чайник на кухне управляется дистанционно через BLE. Low energy, кстати, гораздо сниженное энергопотребление в отличие от "голого" Bluetooth, настолько сниженное, что устройство готово общаться по данному протоколу на одной батарейке несколько месяцев, а то и лет. Конечно, цитировать и переписывать спецификацию протокола BLE 5.2 нет никакого смысла, поэтому ограничимся основными понятиями.Центральное и периферийное устройство В зависимости от использования и назначения, устройство Bluetooth может быть:
- Центральным (главным) - получает данные от периферийного устройства (наш телефон)
- Периферийным - устройство, которое отправляет данные на центральное устройство (датчик ЧСС)
Рекламные пакеты данных протоколаРекламные или оповещательные данные отправляются с периферийного устройства в виде пакетов, которые содержат в себе основную информацию об устройстве: его название, а также его функциональные возможности. Задача центрального устройства, получить их, прочитать, а после выделить из списка периферии необходимый для подключения девайс.Объем рекламных пакетов не очень большой и вместить всю информацию об устройстве не способен. Чтобы получить доступ ко всем возможностям и характеристикам устройства, необходимо выполнить подключение, после чего считать его данные, которые, в свою очередь, могут быть предоставлены в виде:
- Сервиса (услуг) - набор данных, описывающих функции устройства. В нашем случае мы увидим службу получения ЧСС.
- Характеристик - дополнительных описаний сервисов устройства. Например характеристика изменения сердечного ритма в секунду, а также положения датчика на теле.
Переходя к абстракциям, сервисом является некий шкаф, в котором много ящиков - характеристик. Причем каждый сервис уникален и представлен идентификатором UUID, который может быть 16-битным или 128-битным, в зависимости от типов сервисов.Перейдем к написанию кодаСоздадим проект в Xcode с одноимённым названием, после чего добавим несколько необходимых Label в Main.storyboard и перетянем outlets этих labels во View Controller, закрепим их с помощью constraints, а также скроем их для первоначального изображения в методе viewDidLoad, как я сделал это на изображении:
Я создал outlets для текстовых значений "121" и "грудь", другие же текстовые значения просто закрепил на view, так как изменений в них делать мы не планируем.
Отладку и демонстрацию работы необходимо совершать на реальном устройстве, так как симулятор не поддерживает возможность работы по протоколу Bluetooth.
В файле Info.plist проекта необходимо добавить свойство: Bluetooth Always Usage Description и прикрепить к нему описание, чтобы уведомить пользователя об использовании данных по Bluetooth при первом запуске приложения. Если данное свойство не добавить в список, то приложение "упадет" с одноименной ошибкой. Не забывайте про это!Подключаем библиотеку BluetoothТут все просто, для подключения библиотеки воспользуемся следующей строчкой:
import CoreBluetooth
Вспомним, что по протоколу у нас существуют так называемые центральные и периферийные устройства, логично предположить, что основной функционал работы данного протокола будет исполнен методами делегатов централи и периферии.Для начала создадим переменную центрального (главного) устройства в проекте рядом с объявлением других переменных:
var centralManager: CBCentralManager!
Теперь, чтобы получить доступ к методам необходимо назначить ViewController делегатом, но предварительно подпишем его под протокол CBCentralManagerDelegate. Сделать это предлагаю в extension ViewController, так будет рациональнее.
extension ViewController: CBCentralManagerDelegate {}
Xcode на такое пользовательское действие отреагирует ошибкой: "Type 'ViewController' does not conform to protocol 'CBCentralManagerDelegate'", оповещая, что данный протокол требует обязательную реализацию метода: "func centralManagerDidUpdateState(_ central: CBCentralManager)". Нажмем "fix", добавив этот метод в проект. Данный метод нужен для автоматической проверки состояния центрального менеджера, которого мы создали ранее.Чтобы отобразить все состояния центрального менеджера, в теле метода "func centralManagerDidUpdateState(_ central: CBCentralManager)" напишем:
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
}
Xcode автоматически предложит вставить все возможные состояния данного условия, соглашаемся с ним. А в каждом из состояний напишем функцию print("это состояние"):
extension ViewController: CBCentralManagerDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .unknown:
print ("central.state is unknown")
case .resetting:
print ("central.state is resetting")
case .unsupported:
print ("central.state is unsupported")
case .unauthorized:
print ("central.state is unauthorized")
case .poweredOff:
print ("central.state is poweredOff")
case .poweredOn:
print ("central.state is poweredOn")
@unknown default:
break
}
}
}
Теперь нам осталось проинициализировать переменную "centralManager" и задать ей делегирование. Сделаем это в методе "viewDidLoad", а в качестве параметра очереди напишем "nil", определяя всю работу про Bluetooth в главной очереди.
override func viewDidLoad() {
super.viewDidLoad()
centralManager = CBCentralManager(delegate: self, queue: nil)
heartRateLabel.isHidden = true
bodyLocationLabel.isHidden = true
}
Собираем проект, запускаем на устройстве с включенным Bluetooth, видим системный запрос за его использование, соглашаемся и получаем в консоль заветное сообщение "central.state is poweredOn", которое сигнализирует нам о том, что центральный менеджер готов к работе. Если выключить Bluetooth на телефоне, то в консоли появится логичное "central.state is poweredOff".Поиск Bluetooth устройствЦентральный менеджер ждет дальнейших указаний, и сейчас он их получит. Для этого в методе "centralManagerDidUpdateState" в случае ".poweredOn" после метода "print" пишем:
centralManager.scanForPeripherals(withServices: nil)
Менеджер начнет сканировать все доступные вокруг устройства, а чтобы мы смогли увидеть их в консоли приложения, необходимо реализовать метод делегата в extension ViewController ниже метода "centralManagerDidUpdateState" следующим образом:
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
print(peripheral)
}
Запускаем приложение... И теперь в консоли мы можем увидеть множество всех доступных для подключения устройств. Замечательно! Одним из них и является необходимый для подключения пульсометр. Но чтобы упростить поиск пульсометра, можно воспользоваться некоторой хитростью, которую я сейчас покажу.Идентификатор служб UUIDЯ ранее упомянул наличие данного идентификатора в протоколе Bluetooth как уникальную характеристику для различных устройств, поэтому могу сказать вам, что пульсометры обладают таким уникальным UUID для своей непосредственной службы измерения ЧСС. Список всех UUID можно также найти в спецификации, из которой я нашел нужный: "0x180D". Добавим новую константу в проект над объявленными ранее outlets:
let heartRateUUID = CBUUID(string: "0x180D")
Также обновим метод "centralManager.scanForPeripherals(withServices: nil)" добавив в него вышенаписанный идентификатор пульсометра:
case .poweredOn:
print ("central.state is poweredOn")
centralManager.scanForPeripherals(withServices: [heartRateUUID] )
Теперь центральный менеджер находится в поиске устройств с данным UUID, и после некоторого времени в консоли появиться заветное устройство:
<CBPeripheral: 0x280214000, identifier = D5A5CD3E-33AC-7245-4294-4FFB9B986DFC, name = COOSPO H6 0062870, state = disconnected>
Теперь необходимо создать переменную в проекте, с которой мы сможем связать данное устройство, для этого рядом с "var centralManager: CBCentralManager!" напишем:
var heartRatePeripheral: CBPeripheral!
А в методе "didDiscover peripheral" свяжем найденное устройство с вышеобъявленной переменной и прекратим поиск новых устройств с помощью метода:
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
print(peripheral)
heartRatePeripheral = peripheral
centralManager.stopScan()
}
Подключаемся к пульсометруДля этого напишем под строкой "centralManager.stopScan()":
centralManager.connect(heartRatePeripheral, options: nil)
Нам уже удалось подключиться к пульсометру, но чтобы это действительно увидеть, необходимо реализовать еще один метод делегата "didConnect peripheral" ниже метода "didDiscover peripheral", который автоматически вызывается при подключении нового устройства:
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print("Соединение установлено")
}
Собираем проект, запускаем на устройстве и видим в консоле заветное "Соединение установлено". Хороший результат, теперь двигаемся дальше.Получаем список сервисов с пульсометраПосле того, как соединение установлено, необходимо сделать запрос об услугах (сервисах), которые данный пульсометр готов предоставить. Для этого после установки соединения вызовем метод "heartRatePeripheral.discoverServices()" в методе "didConnect", который примет следующий вид:
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print("Соединение установлено")
heartRatePeripheral.discoverServices(nil)
}
Запрос на получение сервисов сделан, а чтобы их увидеть и начать с ними работать, необходимо расширить класс протоколом "CBPeripheralDelegate" в самом низу нашего проекта и вызвать метод "peripheral(_:didDiscoverServices:)" следующим образом:
extension ViewController: CBPeripheralDelegate {
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
guard let services = peripheral.services else { return }
for service in services {
print(service)
}
}
}
Метод получает сервисы, сообщает об этом центральному менеджеру и выводит их в консоль. В данный момент консоль будет пуста, так как необходимо делегировать данный протокол периферийному устройству "heartRatePeripheral". Сделаем это после инициализации периферийного устройства в следующем методе:
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
print(peripheral)
heartRatePeripheral = peripheral
heartRatePeripheral.delegate = self
centralManager.stopScan()
centralManager.connect(heartRatePeripheral, options: nil)
}
Отлично, делегат обьявлен, метод получения сервисов написан, запустим программу на телефоне и получим в консоль список служб пульсометра:
<CBService: 0x2824b4340, isPrimary = YES, UUID = Heart Rate><CBService: 0x2824b4240, isPrimary = YES, UUID = Battery><CBService: 0x2824b4280, isPrimary = YES, UUID = Device Information><CBService: 0x2824b4200, isPrimary = YES, UUID = 8FC3FD00-F21D-11E3-976C-0002A5D5C51B>
Не все сервисы нам интересны и оставить необходимо лишь первый. Для этого можно провести так называемую фильтрацию с помощью идентификатора UUID в методе "heartRatePeripheral.discoverServices()"
heartRatePeripheral.discoverServices([heartRateUUID])
Вот теперь список служб отобразится в виде "<CBService: 0x2824b4340, isPrimary = YES, UUID = Heart Rate>", из которой мы сможем извлечь нужные нам характеристики - ящики (№ шкафа мы уже получили).Достаем характеристики из шкафаШкаф-сервис нам известен, осталось посмотреть, что он предлагает и получить это. Сделаем запрос на получение характеристик, для этого в теле метода "didDiscoverServices - peripheral" реализуем метод - поиск:
extension ViewController: CBPeripheralDelegate {
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
guard let services = peripheral.services else { return }
for service in services {
peripheral.discoverCharacteristics(nil, for: service)
}
}
}
Теперь доступный сервис будет посылать свои характеристики, а увидеть мы их сможем в самостоятельном методе делегата "CBPeripheralDelegate" под названием "didDiscoverCharacteristicsFor". Реализуем его и выведем в консоль все доступные характеристики:
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
guard let characteristics = service.characteristics else { return }
for characteristic in characteristics {
print(characteristic)
}
}
Запускаем программу, видим, что характеристики получены, а консоль заполнилась следующими строками:
<CBCharacteristic: 0x28024c120, UUID = 2A37, properties = 0x10, value = {length = 2, bytes = 0x0469}, notifying = NO><CBCharacteristic: 0x28024c180, UUID = 2A38, properties = 0x2, value = {length = 1, bytes = 0x01}, notifying = NO>
Видно, что у данной службы две характеристики, имеющие два уникальных идентификатора. Из спецификации на Bluetooth узнаем, что UUID = 2A37 отвечает за измерение ЧСС, а UUID = 2A38 за положение датчика на теле. Положение датчика на теле не самая интересная характеристика в данной теме, но будет полезно считать и ее. Для удобства добавим в проект два уникальных идентификатора данных характеристик следующим образом:
let heartRateUUID = CBUUID(string: "0x180D")
let heartRateCharacteristicCBUUID = CBUUID(string: "2A37")
let bodyLocationCharacteristicCBUUID = CBUUID(string: "2A38")
Характеристики отличаются друг от друга типами свойств. Например, характеристика ЧСС имеет свойство ".notify" т.е. она уведомляет об изменении значения ЧСС, а характеристика положения на теле имеет свойство ".read", т.е. может быть считана напрямую. Данное пояснение необходимо, чтобы правильно получить значения из них.Положение пульсометра на телеХарактеристика выведена консоль, теперь нужно лишь реализовать метода считывая значений из нее. Для этого напишем запрос на чтение значений "peripheral.readValue(for: characteristic)"
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
guard let characteristics = service.characteristics else { return }
for characteristic in characteristics {
peripheral.readValue(for: characteristic)
}
}
Запрос написан, как вы догадываетесь, нужно реализовать еще один метод "peripheral(_:didUpdateValueFor:error:)" делегата "CBPeripheralDelegate", который будет в асинхронном режиме получать ответ с данного запроса, причем в данном методе напишем конструкцию "switch - case", чтобы была возможность разделить характеристики по уникальному идентификатору:
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic,
error: Error?) {
switch characteristic.uuid {
case bodySensorLocationCharacteristicCBUUID:
print(characteristic.value ?? "no value")
default:
print("Unhandled Characteristic UUID: \(characteristic.uuid)")
}
}
В консоли после выполнения данной программы появится строка "1 bytes". Это нужный результат, потому что мы пытались вывести объект типа "data".Чтобы "распасить" данный байт, необходимо снова прибегнуть к спецификации, из которой мы сможем понять, как данный байт заполнен. Ускоряя результат, реализуем ниже функцию получения строки положения на теле датчика ЧСС из этого байта:
private func bodyLocation(from characteristic: CBCharacteristic) -> String {
guard let characteristicData = characteristic.value,
let byte = characteristicData.first else { return "Error" }
switch byte {
case 0: return "Другое"
case 1: return "Грудь"
case 2: return "Запястье"
case 3: return "Палец"
case 4: return "Ладонь"
case 5: return "Мочка уха"
case 6: return "Нога"
default:
return "Резерв"
}
}
И теперь вызовем данную функцию в методе "didUpdateValueFor characteristic", одновременно выводя результат на экран телефона (не забудем показать скрытый label для положения датчика):
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic,
error: Error?) {
switch characteristic.uuid {
case bodyLocationCharacteristicCBUUID:
let bodySensorLocation = bodyLocation(from: characteristic)
bodyLocationLabel.text = bodySensorLocation
bodyLocationLabel.isHidden = false
default:
print("Unhandled Characteristic UUID: \(characteristic.uuid)")
}
}
Ура! Характеристика успешно получена, прочитана и выведена на экран!
Не совсем ясно, где еще можно носить данный пульсометр, поэтому существует данная характеристика :)Получение ЧСС и вывод на экран пользователяОсталось совсем немного, и теперь нужно получить значения из характеристики ЧСС. Как мы помним, у нее тип значения ".notify", поэтому нам нужно как бы "подписаться на нее", чтобы она присылала обновленные значения ЧСС. Для этого нужно выполнить метод "peripheral.setNotifyValue(true, for: characteristic)" в функции "didDiscoverCharacteristicsFor service:
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
guard let characteristics = service.characteristics else { return }
for characteristic in characteristics {
peripheral.readValue(for: characteristic)
peripheral.setNotifyValue(true, for: characteristic)
}
}
Если запустить приложение, то в консоли появятся стоки:
Unhandled Characteristic UUID: 2A37Unhandled Characteristic UUID: 2A37Unhandled Characteristic UUID: 2A37
Именно в этой характеристики и лежат данные о ЧСС. Теперь необходимо провернуть такую же развертку этих данных, обращаясь к спецификации. В некоторых моделях данные могут быть представлены либо 1 либо 2 байтами. Чтобы не получить конфуз, реализуем метод для "парсинга" этих данных в нужном порядке в протоколе "CBPeripheralDelegate".
private func heartRate(from characteristic: CBCharacteristic) -> Int {
guard let characteristicData = characteristic.value else { return -1 }
let byteArray = [UInt8](characteristicData)
let firstBitValue = byteArray[0] & 0x01
if firstBitValue == 0 {
return Int(byteArray[1])
} else {
return (Int(byteArray[1]) << 8) + Int(byteArray[2])
}
}
И, наконец, добавим еще один case в методе "peripheral(_:didUpdateValueFor:error:)", в котором получим ЧСС, а также обновим и покажем label пользовательского интерфейса:
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic,
error: Error?) {
switch characteristic.uuid {
case bodyLocationCharacteristicCBUUID:
let bodySensorLocation = bodyLocation(from: characteristic)
bodyLocationLabel.text = bodySensorLocation
bodyLocationLabel.isHidden = false
case heartRateCharacteristicCBUUID:
let bpm = heartRate(from: characteristic)
heartRateLabel.text = String(bpm)
heartRateLabel.isHidden = false
default:
print("Unhandled Characteristic UUID: \(characteristic.uuid)")
}
}
Поздравляю!
Теперь данные с пульсометра выводятся на экран телефона. Я даже слегка нервничаю :)ИтогиВ целом гайд по использованию Bluetooth для подключения датчика ЧСС вышел немного большим и местами сложным, надеюсь, что основной смысл мне удалось донести. Конечно, есть еще несколько нереализованных методов, которые можно было бы добавить (например, метод переподключения при обрыве соединения), но я посчитал этого набора достаточным, чтобы в меру оценить лаконичность и удобность библиотеки на swift CoreBluetooth.
Всем успехов и спасибо!
===========
Источник:
habr.com
===========
Похожие новости:
- [Смартфоны, Голосовые интерфейсы] Microsoft отказалась от поддержки Cortana для iOS и Android
- [Разработка под iOS, Интерфейсы] Почему мы не обновляли приложение ВКонтакте для iPad пять лет, а теперь обновили
- Google развивает новый Bluetooth-стек для Android, написанный на Rust
- [Разработка мобильных приложений, Управление продуктом, Интервью] Из разработчика в продакты
- [Разработка под iOS, Разработка мобильных приложений, Разработка под Android, Flutter] Программа Mobius: Android, iOS и всё, что между ними
- [Информационная безопасность, Разработка под iOS, Разработка под Android, Исследования и прогнозы в IT] Исследование: Android отправляет в Google в 20 раз больше данных, чем iOS — в Apple
- [Разработка под iOS, Монетизация мобильных приложений, Законодательство в IT] Apple может пойти на мировую с российскими антимонопольщиками
- [Разработка мобильных приложений, Разработка под Android] CameraX+ML Kit для распознавания номера карты в действии
- [] Настало время офигительных историй [1/2]
- [Разработка под iOS, Разработка мобильных приложений, Разработка под Android, Управление проектами, Управление продуктом] 13 подвохов мобильного приложения, о которых лучше знать до старта разработки
Теги для поиска: #_razrabotka_pod_ios (Разработка под iOS), #_razrabotka_mobilnyh_prilozhenij (Разработка мобильных приложений), #_swift, #_swift, #_bluetooth, #_corebluetooth, #_ios, #_pulsometr (пульсометр), #_mobilnaja_razrabotka (мобильная разработка), #_datchik_pulsa (датчик пульса), #_razrabotka_pod_ios (
Разработка под iOS
), #_razrabotka_mobilnyh_prilozhenij (
Разработка мобильных приложений
), #_swift
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 07:28
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
С чего все началось? Около года назад я приобрел данный девайс для контроля частоты сердечных сокращений (далее - ЧСС) во время тренировок. Датчик отлично подсоединяется к телефону, умным часам по Bluetooth, но обычно, фитнесс-приложения, анализирующие подобного рода данные требуют либо подписки, либо нагружены излишне сложными аналитиками, которые мне, как рядовому пользователю не очень интересны. Поэтому у меня родилась идея написать свое приложение для контроля ЧСС во время тренировок для IOS на Swift.Немного теории о технологии Bluetooth LEBluetooth Low Energy - очень популярный и распространённый протокол обмена данными, который мы используем повсеместно и который становится все популярнее с каждым днем. У меня даже чайник на кухне управляется дистанционно через BLE. Low energy, кстати, гораздо сниженное энергопотребление в отличие от "голого" Bluetooth, настолько сниженное, что устройство готово общаться по данному протоколу на одной батарейке несколько месяцев, а то и лет. Конечно, цитировать и переписывать спецификацию протокола BLE 5.2 нет никакого смысла, поэтому ограничимся основными понятиями.Центральное и периферийное устройство В зависимости от использования и назначения, устройство Bluetooth может быть:
Я создал outlets для текстовых значений "121" и "грудь", другие же текстовые значения просто закрепил на view, так как изменений в них делать мы не планируем. Отладку и демонстрацию работы необходимо совершать на реальном устройстве, так как симулятор не поддерживает возможность работы по протоколу Bluetooth.
В файле Info.plist проекта необходимо добавить свойство: Bluetooth Always Usage Description и прикрепить к нему описание, чтобы уведомить пользователя об использовании данных по Bluetooth при первом запуске приложения. Если данное свойство не добавить в список, то приложение "упадет" с одноименной ошибкой. Не забывайте про это!Подключаем библиотеку BluetoothТут все просто, для подключения библиотеки воспользуемся следующей строчкой: import CoreBluetooth
var centralManager: CBCentralManager!
extension ViewController: CBCentralManagerDelegate {}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state { } extension ViewController: CBCentralManagerDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) { switch central.state { case .unknown: print ("central.state is unknown") case .resetting: print ("central.state is resetting") case .unsupported: print ("central.state is unsupported") case .unauthorized: print ("central.state is unauthorized") case .poweredOff: print ("central.state is poweredOff") case .poweredOn: print ("central.state is poweredOn") @unknown default: break } } } override func viewDidLoad() {
super.viewDidLoad() centralManager = CBCentralManager(delegate: self, queue: nil) heartRateLabel.isHidden = true bodyLocationLabel.isHidden = true } centralManager.scanForPeripherals(withServices: nil)
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
print(peripheral) } let heartRateUUID = CBUUID(string: "0x180D")
case .poweredOn:
print ("central.state is poweredOn") centralManager.scanForPeripherals(withServices: [heartRateUUID] ) <CBPeripheral: 0x280214000, identifier = D5A5CD3E-33AC-7245-4294-4FFB9B986DFC, name = COOSPO H6 0062870, state = disconnected>
var heartRatePeripheral: CBPeripheral!
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
print(peripheral) heartRatePeripheral = peripheral centralManager.stopScan() } centralManager.connect(heartRatePeripheral, options: nil)
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print("Соединение установлено") } func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print("Соединение установлено") heartRatePeripheral.discoverServices(nil) } extension ViewController: CBPeripheralDelegate {
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { guard let services = peripheral.services else { return } for service in services { print(service) } } } func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
print(peripheral) heartRatePeripheral = peripheral heartRatePeripheral.delegate = self centralManager.stopScan() centralManager.connect(heartRatePeripheral, options: nil) } <CBService: 0x2824b4340, isPrimary = YES, UUID = Heart Rate><CBService: 0x2824b4240, isPrimary = YES, UUID = Battery><CBService: 0x2824b4280, isPrimary = YES, UUID = Device Information><CBService: 0x2824b4200, isPrimary = YES, UUID = 8FC3FD00-F21D-11E3-976C-0002A5D5C51B>
heartRatePeripheral.discoverServices([heartRateUUID])
extension ViewController: CBPeripheralDelegate {
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { guard let services = peripheral.services else { return } for service in services { peripheral.discoverCharacteristics(nil, for: service) } } } func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
guard let characteristics = service.characteristics else { return } for characteristic in characteristics { print(characteristic) } } <CBCharacteristic: 0x28024c120, UUID = 2A37, properties = 0x10, value = {length = 2, bytes = 0x0469}, notifying = NO><CBCharacteristic: 0x28024c180, UUID = 2A38, properties = 0x2, value = {length = 1, bytes = 0x01}, notifying = NO>
let heartRateUUID = CBUUID(string: "0x180D")
let heartRateCharacteristicCBUUID = CBUUID(string: "2A37") let bodyLocationCharacteristicCBUUID = CBUUID(string: "2A38") func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
guard let characteristics = service.characteristics else { return } for characteristic in characteristics { peripheral.readValue(for: characteristic) } } func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic,
error: Error?) { switch characteristic.uuid { case bodySensorLocationCharacteristicCBUUID: print(characteristic.value ?? "no value") default: print("Unhandled Characteristic UUID: \(characteristic.uuid)") } } private func bodyLocation(from characteristic: CBCharacteristic) -> String {
guard let characteristicData = characteristic.value, let byte = characteristicData.first else { return "Error" } switch byte { case 0: return "Другое" case 1: return "Грудь" case 2: return "Запястье" case 3: return "Палец" case 4: return "Ладонь" case 5: return "Мочка уха" case 6: return "Нога" default: return "Резерв" } } func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic,
error: Error?) { switch characteristic.uuid { case bodyLocationCharacteristicCBUUID: let bodySensorLocation = bodyLocation(from: characteristic) bodyLocationLabel.text = bodySensorLocation bodyLocationLabel.isHidden = false default: print("Unhandled Characteristic UUID: \(characteristic.uuid)") } } Не совсем ясно, где еще можно носить данный пульсометр, поэтому существует данная характеристика :)Получение ЧСС и вывод на экран пользователяОсталось совсем немного, и теперь нужно получить значения из характеристики ЧСС. Как мы помним, у нее тип значения ".notify", поэтому нам нужно как бы "подписаться на нее", чтобы она присылала обновленные значения ЧСС. Для этого нужно выполнить метод "peripheral.setNotifyValue(true, for: characteristic)" в функции "didDiscoverCharacteristicsFor service: func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
guard let characteristics = service.characteristics else { return } for characteristic in characteristics { peripheral.readValue(for: characteristic) peripheral.setNotifyValue(true, for: characteristic) } } Unhandled Characteristic UUID: 2A37Unhandled Characteristic UUID: 2A37Unhandled Characteristic UUID: 2A37
private func heartRate(from characteristic: CBCharacteristic) -> Int {
guard let characteristicData = characteristic.value else { return -1 } let byteArray = [UInt8](characteristicData) let firstBitValue = byteArray[0] & 0x01 if firstBitValue == 0 { return Int(byteArray[1]) } else { return (Int(byteArray[1]) << 8) + Int(byteArray[2]) } } func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic,
error: Error?) { switch characteristic.uuid { case bodyLocationCharacteristicCBUUID: let bodySensorLocation = bodyLocation(from: characteristic) bodyLocationLabel.text = bodySensorLocation bodyLocationLabel.isHidden = false case heartRateCharacteristicCBUUID: let bpm = heartRate(from: characteristic) heartRateLabel.text = String(bpm) heartRateLabel.isHidden = false default: print("Unhandled Characteristic UUID: \(characteristic.uuid)") } } Теперь данные с пульсометра выводятся на экран телефона. Я даже слегка нервничаю :)ИтогиВ целом гайд по использованию Bluetooth для подключения датчика ЧСС вышел немного большим и местами сложным, надеюсь, что основной смысл мне удалось донести. Конечно, есть еще несколько нереализованных методов, которые можно было бы добавить (например, метод переподключения при обрыве соединения), но я посчитал этого набора достаточным, чтобы в меру оценить лаконичность и удобность библиотеки на swift CoreBluetooth. Всем успехов и спасибо!
=========== Источник: habr.com =========== Похожие новости:
Разработка под iOS ), #_razrabotka_mobilnyh_prilozhenij ( Разработка мобильных приложений ), #_swift |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 07:28
Часовой пояс: UTC + 5