У меня есть приложение для 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]
Да, вам нужно поменять UUID приема и передачи на одну или другую сторону; приложение iOS должно записать характеристику, которую получает пи, и наоборот
Спасибо, Пол, это фактически решило мою проблему с получением данных.
Наиболее вероятная причина в том, что ваша рекламная нагрузка слишком велика. Максимальная полезная нагрузка в «обычной» (нерасширенной) рекламе — 31 байт. Ваша полезная нагрузка составляет 39 байт. Вам необходимо изменить свое локальное имя на более короткое или отправить сокращенное локальное имя (0x08). Если вы рекламируете 128-битную службу, можно указать только имя из 8 символов.
Хотя, вероятно, проблема не в этом, параметр интервала gap_advertise
измеряется в микросекундах, а не в миллисекундах. Вы запрашиваете слишком короткий рекламный интервал. Я ожидаю, что оно либо округляется до 625 мкс, либо ему присваивается значение по умолчанию.
Подобная настройка будет работать между двумя iPhone по двум причинам:
Спасибо, в этом и была проблема: полезная нагрузка была слишком тяжелой из-за названия, поэтому я сократил ее до «PicoW», а затем UUID службы был правильно объявлен. Затем я понял, что данные не были получены, потому что я использовал характеристику tx вместо rx в центральной функции sendData. Теперь это работает!! еще раз спасибо
Я не знаю насчет кода Rasberry, но если он не рекламирует целевой сервис, то
scanForPeripherals(withServices: [picServiceUUID])
не будет его показывать, поскольку он его не рекламирует. Вы пишете с ответом, так что же говорит метод делегированияperipheral(_:didWriteValueFor:error:)
? В настоящее времяcatch
используется только потому, что вы не можете преобразовать словарь в JSON, ничего об отправке данных. Но читать ваш код не следует. ты умеешь писать только на rx, а не на tx?