add wifi request ble characteristic

This commit is contained in:
2026-01-10 14:23:39 +00:00
parent cb6f36924f
commit 4010ba8870
6 changed files with 197 additions and 47 deletions

View File

@@ -16,6 +16,8 @@ final class BlePeripheralManager: NSObject, ObservableObject {
static let serviceUUID = CBUUID(string: "A0B0C0D0-E0F0-4A0B-9C0D-0E0F1A2B3C4D")
static let feedTxUUID = CBUUID(string: "A0B0C0D1-E0F0-4A0B-9C0D-0E0F1A2B3C4D")
static let controlRxUUID = CBUUID(string: "A0B0C0D2-E0F0-4A0B-9C0D-0E0F1A2B3C4D")
// Read/Notify: 1-byte value (0x00=OFF, 0x01=ON) that requests Glass to enable WiFi.
static let wifiRequestTxUUID = CBUUID(string: "A0B0C0D3-E0F0-4A0B-9C0D-0E0F1A2B3C4D")
private static let restoreIdentifier = "iris.ble.peripheral.v1"
@Published private(set) var bluetoothState: CBManagerState = .unknown
@@ -30,6 +32,7 @@ final class BlePeripheralManager: NSObject, ObservableObject {
@Published private(set) var droppedNotifyPackets: Int = 0
@Published private(set) var lastNotifyAt: Date? = nil
@Published private(set) var lastDataAt: Date? = nil
@Published private(set) var wifiRequested: Bool = false
private let queue = DispatchQueue(label: "iris.ble.peripheral.queue")
private lazy var peripheral = CBPeripheralManager(
@@ -43,10 +46,13 @@ final class BlePeripheralManager: NSObject, ObservableObject {
private var service: CBMutableService?
private var feedTx: CBMutableCharacteristic?
private var controlRx: CBMutableCharacteristic?
private var wifiRequestTx: CBMutableCharacteristic?
private var subscribedCentralIds = Set<UUID>()
private var centralMaxUpdateLength: [UUID: Int] = [:]
private var lastReadValue: Data = Data()
private var wifiRequestValue: Data = Data([0x00])
private var wifiRequestNotifyPending: Bool = false
private struct PendingMessage {
let msgId: UInt32
@@ -101,11 +107,23 @@ final class BlePeripheralManager: NSObject, ObservableObject {
}
}
func setWifiRequested(_ requested: Bool) {
queue.async { [weak self] in
guard let self else { return }
let newValue = Data([requested ? 0x01 : 0x00])
guard newValue != self.wifiRequestValue else { return }
self.wifiRequestValue = newValue
self.publish { self.wifiRequested = requested }
self.flushWifiRequestNotifyIfPossible()
}
}
func copyUUIDsToPasteboard() {
let text = """
SERVICE_UUID=\(Self.serviceUUID.uuidString)
FEED_TX_UUID=\(Self.feedTxUUID.uuidString)
CONTROL_RX_UUID=\(Self.controlRxUUID.uuidString)
WIFI_REQUEST_TX_UUID=\(Self.wifiRequestTxUUID.uuidString)
"""
#if canImport(UIKit)
DispatchQueue.main.async {
@@ -116,6 +134,18 @@ final class BlePeripheralManager: NSObject, ObservableObject {
private func ensureService() {
guard peripheral.state == .poweredOn else { return }
if let existingService = service {
let hasWifiChar = (existingService.characteristics ?? []).contains { $0.uuid == Self.wifiRequestTxUUID }
if wifiRequestTx == nil || !hasWifiChar {
stopAdvertising()
peripheral.removeAllServices()
service = nil
feedTx = nil
controlRx = nil
wifiRequestTx = nil
}
}
guard service == nil else { return }
let feedTx = CBMutableCharacteristic(
@@ -130,11 +160,18 @@ final class BlePeripheralManager: NSObject, ObservableObject {
value: nil,
permissions: [.writeable]
)
let wifiRequestTx = CBMutableCharacteristic(
type: Self.wifiRequestTxUUID,
properties: [.notify, .read],
value: nil,
permissions: [.readable]
)
let service = CBMutableService(type: Self.serviceUUID, primary: true)
service.characteristics = [feedTx, controlRx]
service.characteristics = [feedTx, controlRx, wifiRequestTx]
self.service = service
self.feedTx = feedTx
self.controlRx = controlRx
self.wifiRequestTx = wifiRequestTx
peripheral.add(service)
}
@@ -278,6 +315,20 @@ final class BlePeripheralManager: NSObject, ObservableObject {
publishNotifyQueueDepth()
}
private func flushWifiRequestNotifyIfPossible() {
guard let wifiRequestTx else { return }
let hasSubscribers = !(wifiRequestTx.subscribedCentrals ?? []).isEmpty
guard hasSubscribers else {
wifiRequestNotifyPending = false
return
}
if peripheral.updateValue(wifiRequestValue, for: wifiRequestTx, onSubscribedCentrals: nil) {
wifiRequestNotifyPending = false
} else {
wifiRequestNotifyPending = true
}
}
private func enqueuePendingMessage(_ message: PendingMessage) {
pendingMessages.append(message)
enforceNotifyQueueLimits()
@@ -334,6 +385,7 @@ extension BlePeripheralManager: CBPeripheralManagerDelegate {
self.service = restoredMutableService
self.feedTx = characteristics.first(where: { $0.uuid == Self.feedTxUUID }) as? CBMutableCharacteristic
self.controlRx = characteristics.first(where: { $0.uuid == Self.controlRxUUID }) as? CBMutableCharacteristic
self.wifiRequestTx = characteristics.first(where: { $0.uuid == Self.wifiRequestTxUUID }) as? CBMutableCharacteristic
}
self.publish {
@@ -362,6 +414,7 @@ extension BlePeripheralManager: CBPeripheralManagerDelegate {
self.service = nil
self.feedTx = nil
self.controlRx = nil
self.wifiRequestTx = nil
return
}
self.ensureService()
@@ -380,6 +433,12 @@ extension BlePeripheralManager: CBPeripheralManagerDelegate {
}
func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didSubscribeTo characteristic: CBCharacteristic) {
if characteristic.uuid == Self.wifiRequestTxUUID {
queue.async { [weak self] in
self?.flushWifiRequestNotifyIfPossible()
}
return
}
guard characteristic.uuid == Self.feedTxUUID else { return }
queue.async { [weak self] in
guard let self = self else { return }
@@ -400,6 +459,9 @@ extension BlePeripheralManager: CBPeripheralManagerDelegate {
}
func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didUnsubscribeFrom characteristic: CBCharacteristic) {
if characteristic.uuid == Self.wifiRequestTxUUID {
return
}
guard characteristic.uuid == Self.feedTxUUID else { return }
queue.async { [weak self] in
guard let self = self else { return }
@@ -420,17 +482,25 @@ extension BlePeripheralManager: CBPeripheralManagerDelegate {
func peripheralManagerIsReady(toUpdateSubscribers peripheral: CBPeripheralManager) {
queue.async { [weak self] in
self?.flushPendingMessages()
if let self, self.wifiRequestNotifyPending {
self.flushWifiRequestNotifyIfPossible()
}
}
}
func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveRead request: CBATTRequest) {
guard request.characteristic.uuid == Self.feedTxUUID else {
peripheral.respond(to: request, withResult: .requestNotSupported)
let maxLen = max(0, request.central.maximumUpdateValueLength)
if request.characteristic.uuid == Self.feedTxUUID {
request.value = maxLen > 0 ? lastReadValue.prefix(maxLen) : lastReadValue
peripheral.respond(to: request, withResult: .success)
return
}
let maxLen = max(0, request.central.maximumUpdateValueLength)
request.value = maxLen > 0 ? lastReadValue.prefix(maxLen) : lastReadValue
peripheral.respond(to: request, withResult: .success)
if request.characteristic.uuid == Self.wifiRequestTxUUID {
request.value = maxLen > 0 ? wifiRequestValue.prefix(maxLen) : wifiRequestValue
peripheral.respond(to: request, withResult: .success)
return
}
peripheral.respond(to: request, withResult: .requestNotSupported)
}
func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) {

View File

@@ -34,6 +34,16 @@ struct BleStatusView: View {
ble.start()
}
Toggle(isOn: Binding(get: { ble.wifiRequested }, set: { ble.setWifiRequested($0) })) {
VStack(alignment: .leading, spacing: 2) {
Text("Request Wi-Fi (Glass)")
.font(.headline)
Text(ble.wifiRequested ? "On" : "Off")
.font(.subheadline)
.foregroundStyle(.secondary)
}
}
VStack(alignment: .leading, spacing: 8) {
Text("Connection")
.font(.headline)
@@ -86,7 +96,7 @@ struct BleStatusView: View {
VStack(alignment: .leading, spacing: 8) {
Text("UUIDs")
.font(.headline)
Text("Service: \(BlePeripheralManager.serviceUUID.uuidString)\nFEED_TX: \(BlePeripheralManager.feedTxUUID.uuidString)\nCONTROL_RX: \(BlePeripheralManager.controlRxUUID.uuidString)")
Text("Service: \(BlePeripheralManager.serviceUUID.uuidString)\nFEED_TX: \(BlePeripheralManager.feedTxUUID.uuidString)\nCONTROL_RX: \(BlePeripheralManager.controlRxUUID.uuidString)\nWIFI_REQUEST_TX: \(BlePeripheralManager.wifiRequestTxUUID.uuidString)")
.font(.caption)
.textSelection(.enabled)
}