Реализация Bluetooth BLE между приложением Swift и Rasberry Pi Pico W

У меня есть приложение для iOS, использующее CoreBluetooth для подключения к Rasberry Pi Pico W. Пытаюсь отправить данные на плату, но данные не получены, хотя соединение установлено правильно. Тот же код Central Manager работает с другим приложением IOS, использующим Peripheral Manager.

У меня такое ощущение, что может возникнуть проблема с тем, как UUID объявляются между двумя устройствами. Это потому, что у меня есть еще одна проблема (возможно, связанная): если я сканирую периферийные устройства из Central, фильтруя определенный UUID( centralManager.scanForPeripherals(withServices: [picServiceUUID], options: nil) ) пико W не найден, а если я оставлю фильтр ( centralManager.scanForPeripherals(withServices: nil, options: nil) ), тогда Central обнаружит пико W и сможет подключиться. Ниже мой код для микропитона и центрального кода Swift. Заранее спасибо всем, кто может помочь.

Микропитон для Pico W

import bluetooth
import time

# Define the service and characteristic UUIDs
SERVICE_UUID = bluetooth.UUID("2035166A-756E-3021-98A1-15242BAAB874")
CHAR_UUID_TX = bluetooth.UUID("AF0BADB1-5B99-43CD-917A-A77BC549E3CC")
CHAR_UUID_RX = bluetooth.UUID("AF0BADB1-5B99-43CD-917A-A77BC549E3CD")

# Initialize BLE
ble = bluetooth.BLE()
ble.active(True)

# Set the device name
device_name = "PicoW_BLE_Device"
ble.config(gap_name=device_name)

# Create a BLE service
service = (SERVICE_UUID, [
    (CHAR_UUID_TX, bluetooth.FLAG_READ | bluetooth.FLAG_NOTIFY),
    (CHAR_UUID_RX, bluetooth.FLAG_WRITE | bluetooth.FLAG_WRITE_NO_RESPONSE),
])
services = ble.gatts_register_services([service])

# Extract handles for the characteristics
tx_handle = services[0][0]
rx_handle = services[0][1]

# Function to reverse the UUID bytes
def reverse_bytes(uuid):
    return bytes(reversed(bytes(uuid)))

# Function to construct advertisement payload
def adv_payload(name, services):
    payload = bytearray()
    # Add Flags
    payload.extend(bytearray([0x02, 0x01, 0x06]))  # General Discoverable Mode
    # Add Complete List of 128-bit Service Class UUIDs
    for uuid in services:
        b = reverse_bytes(uuid)
        payload.extend(bytearray([len(b) + 1, 0x07]) + b)
    # Add Complete Local Name
    payload.extend(bytearray([len(name) + 1, 0x09]) + name.encode())
    return payload

# Construct the advertisement data
adv_data = adv_payload(device_name, [SERVICE_UUID])

# Log the advertisement data for debugging
print("Advertisement Data (hex):", adv_data.hex())

# Callback for handling BLE events
def irq(event, data):
    print(data)
    if event == 1:  # Central connected
        conn_handle, addr_type, addr = data
        addr_str = ':'.join('{:02x}'.format(b) for b in addr)
        print(f"Connected to central: {addr_str}")

    elif event == 2:  # Central disconnected
        conn_handle, addr_type, addr = data
        addr_str = ':'.join('{:02x}'.format(b) for b in addr)
        print(f"Disconnected from central: {addr_str}")
        # Restart advertising
        ble.gap_advertise(advertisement_interval_ms, adv_data, connectable=True)

    elif event == 3:  # GATT server write event
        conn_handle, attr_handle = data
        if attr_handle == rx_handle:
            request = ble.gatts_read(rx_handle)
            data = bytes(request)
            print(f"Received data: {data.decode()}")
            # Respond to the central device
            response = "Data received"
            ble.gatts_notify(conn_handle, tx_handle, response.encode())

# Set the IRQ handler
ble.irq(irq)

# Set the advertisement interval in milliseconds
advertisement_interval_ms = 100  # 100ms interval

# Main loop for advertising
while True:
    print(f"Advertising as '{device_name}' with interval {advertisement_interval_ms}ms...")
    
    # Start advertising with the constructed payload and interval
    ble.gap_advertise(advertisement_interval_ms, adv_data, connectable=True)
    
    # Wait before advertising again
    time.sleep(1)

Вот диспетчер Bluetooth в Swift

import Foundation
import CoreBluetooth

class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeripheralDelegate {
    var centralManager: CBCentralManager!
    var connectedPeripheral: CBPeripheral?
    var txCharacteristic: CBCharacteristic?
    
    let picoServiceUUID = CBUUID(string: "2035166A-756E-3021-98A1-15242BAAB874")
    let txCharacteristicUUID = CBUUID(string: "AF0BADB1-5B99-43CD-917A-A77BC549E3CC")
    let rxCharacteristicUUID = CBUUID(string: "AF0BADB1-5B99-43CD-917A-A77BC549E3CD")
    
    @Published var peripherals: [CBPeripheral] = []
    @Published var isConnected = false
    @Published var statusMessage = "Scanning for devices..."
    
    override init() {
        super.init()
        centralManager = CBCentralManager(delegate: self, queue: nil)
    }
    
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        if central.state == .poweredOn {
            centralManager.scanForPeripherals(withServices: nil, options: nil)
        } else {
            statusMessage = "Bluetooth not available."
        }
    }
    
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        if peripheral.name == "PicoW_BLE_Device" {
            print("payload \(advertisementData)")
        }
        if !peripherals.contains(where: { $0.identifier == peripheral.identifier }) {
            peripherals.append(peripheral)
        }
    }
    
    func connectToPeripheral(peripheral: CBPeripheral) {
        connectedPeripheral = peripheral
        connectedPeripheral?.delegate = self
        centralManager.stopScan()
        centralManager.connect(peripheral, options: nil)
        statusMessage = "Connecting to \(peripheral.name ?? "device")..."
    }
    
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        peripheral.discoverServices(nil)
        statusMessage = "Connected to \(peripheral.name ?? "device")."
        isConnected = true
    }
    
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        guard let services = peripheral.services else { return }
        for service in services {
            peripheral.discoverCharacteristics(nil, for: service)
        }
    }
    
    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        statusMessage = "Disconnected from \(peripheral.name ?? "device")."
        isConnected = false
        if let error = error {
            statusMessage += " Error: \(error.localizedDescription)"
        }
        // Optionally, you can restart scanning here
        centralManager.scanForPeripherals(withServices: nil, options: nil)
    }
    
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        guard let characteristics = service.characteristics else { return }
        for characteristic in characteristics {
            if characteristic.properties.contains(.write) || characteristic.properties.contains(.writeWithoutResponse) {
                // RX Characteristic
                print("Found RX Characteristic: \(characteristic.uuid)")
            }
            if characteristic.properties.contains(.notify) {
                // TX Characteristic
                print("Found TX Characteristic: \(characteristic.uuid)")
            }
            if characteristic.uuid == txCharacteristicUUID {
                txCharacteristic = characteristic
                statusMessage = "Ready to send data."
            }
        }
    }
    
    func sendData(_ dictionary: [String: Any]) {
        guard let txCharacteristic = txCharacteristic, let peripheral = connectedPeripheral else { return }
        do {
            let data = try JSONSerialization.data(withJSONObject: dictionary, options: [])
            peripheral.writeValue(data, for: txCharacteristic, type: .withResponse)
            statusMessage = "Sent data: \(dictionary)"
        } catch {
            statusMessage = "Failed to send data: \(error.localizedDescription)"
            print("Failed to send data: \(error.localizedDescription)")
        }
    }
}

Дополнительная информация: вот рекламируемые данные от picoW, обнаруженные приложением, и похоже, что UUID службы отсутствует. ["kCBAdvDataTimestamp": 740772445.177747, "kCBAdvDataRxSecondaryPHY": 0, "kCBAdvDataRxPrimaryPHY": 129, "kCBAdvDataIsConnectable": 1]

Я не знаю насчет кода Rasberry, но если он не рекламирует целевой сервис, то scanForPeripherals(withServices: [picServiceUUID]) не будет его показывать, поскольку он его не рекламирует. Вы пишете с ответом, так что же говорит метод делегирования peripheral(_:didWriteValueFor:error:)? В настоящее время catch используется только потому, что вы не можете преобразовать словарь в JSON, ничего об отправке данных. Но читать ваш код не следует. ты умеешь писать только на rx, а не на tx?

Larme 22.06.2024 21:17

Да, вам нужно поменять UUID приема и передачи на одну или другую сторону; приложение iOS должно записать характеристику, которую получает пи, и наоборот

Paulw11 22.06.2024 23:16

Спасибо, Пол, это фактически решило мою проблему с получением данных.

E. Tocchi 23.06.2024 18:13
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
3
72
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Наиболее вероятная причина в том, что ваша рекламная нагрузка слишком велика. Максимальная полезная нагрузка в «обычной» (нерасширенной) рекламе — 31 байт. Ваша полезная нагрузка составляет 39 байт. Вам необходимо изменить свое локальное имя на более короткое или отправить сокращенное локальное имя (0x08). Если вы рекламируете 128-битную службу, можно указать только имя из 8 символов.

Хотя, вероятно, проблема не в этом, параметр интервала gap_advertise измеряется в микросекундах, а не в миллисекундах. Вы запрашиваете слишком короткий рекламный интервал. Я ожидаю, что оно либо округляется до 625 мкс, либо ему присваивается значение по умолчанию.

Подобная настройка будет работать между двумя iPhone по двум причинам:

  • Все современные iPhone поддерживают расширенную рекламу BLEv5, которая позволяет использовать рекламные пакеты размером до 254 байт. (Хотя Pico W поддерживает Bluetooth 5, я не верю, что он поддерживает расширенную рекламу. Если это так, вы можете использовать эту функцию.)
  • В iOS есть собственный трюк для UUID рекламных сервисов в так называемой «области переполнения» через фильтр Блума (или что-то очень похожее).

Спасибо, в этом и была проблема: полезная нагрузка была слишком тяжелой из-за названия, поэтому я сократил ее до «PicoW», а затем UUID службы был правильно объявлен. Затем я понял, что данные не были получены, потому что я использовал характеристику tx вместо rx в центральной функции sendData. Теперь это работает!! еще раз спасибо

E. Tocchi 23.06.2024 18:11

Другие вопросы по теме