Read MAC address during provisioning and use as hardware_id
- Modified BeaconProvisioner to read device info (0x30) before writing config - Extract MAC address from beacon and return in ProvisioningResult - Use MAC address as hardware_id field (snake_case for backend) - Reorder scan view: Configurable Devices section now appears first - Add debug logging for beacon registration Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
2ec195243c
commit
02592ea249
3 changed files with 120 additions and 32 deletions
|
|
@ -341,7 +341,7 @@ class Api {
|
|||
"UUID": uuid,
|
||||
"Major": major,
|
||||
"Minor": minor,
|
||||
"HardwareID": hardwareId
|
||||
"hardware_id": hardwareId
|
||||
]
|
||||
if let mac = macAddress, !mac.isEmpty {
|
||||
body["MACAddress"] = mac
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import CoreBluetooth
|
|||
|
||||
/// Result of a provisioning operation
|
||||
enum ProvisioningResult {
|
||||
case success
|
||||
case success(macAddress: String?)
|
||||
case failure(String)
|
||||
}
|
||||
|
||||
|
|
@ -123,6 +123,8 @@ class BeaconProvisioner: NSObject, ObservableObject {
|
|||
private var dxSmartNotifySubscribed = false
|
||||
private var dxSmartCommandQueue: [Data] = []
|
||||
private var dxSmartWriteIndex = 0
|
||||
private var provisioningMacAddress: String?
|
||||
private var awaitingDeviceInfoForProvisioning = false
|
||||
|
||||
// Read config mode
|
||||
private enum OperationMode { case provisioning, readingConfig }
|
||||
|
|
@ -178,6 +180,8 @@ class BeaconProvisioner: NSObject, ObservableObject {
|
|||
self.dxSmartNotifySubscribed = false
|
||||
self.dxSmartCommandQueue.removeAll()
|
||||
self.dxSmartWriteIndex = 0
|
||||
self.provisioningMacAddress = nil
|
||||
self.awaitingDeviceInfoForProvisioning = false
|
||||
self.connectionRetryCount = 0
|
||||
self.currentBeacon = beacon
|
||||
|
||||
|
|
@ -256,6 +260,8 @@ class BeaconProvisioner: NSObject, ObservableObject {
|
|||
dxSmartNotifySubscribed = false
|
||||
dxSmartCommandQueue.removeAll()
|
||||
dxSmartWriteIndex = 0
|
||||
provisioningMacAddress = nil
|
||||
awaitingDeviceInfoForProvisioning = false
|
||||
connectionRetryCount = 0
|
||||
currentBeacon = nil
|
||||
state = .idle
|
||||
|
|
@ -273,12 +279,13 @@ class BeaconProvisioner: NSObject, ObservableObject {
|
|||
}
|
||||
|
||||
private func succeed() {
|
||||
DebugLog.shared.log("BLE: Success!")
|
||||
DebugLog.shared.log("BLE: Success! MAC=\(provisioningMacAddress ?? "unknown")")
|
||||
state = .success
|
||||
if let peripheral = peripheral {
|
||||
centralManager.cancelPeripheralConnection(peripheral)
|
||||
}
|
||||
completion?(.success)
|
||||
let mac = provisioningMacAddress
|
||||
completion?(.success(macAddress: mac))
|
||||
cleanup()
|
||||
}
|
||||
|
||||
|
|
@ -327,6 +334,32 @@ class BeaconProvisioner: NSObject, ObservableObject {
|
|||
peripheral?.writeValue(passwordData, for: passwordChar, type: .withResponse)
|
||||
}
|
||||
|
||||
/// Read device info (MAC address) before writing config
|
||||
private func dxSmartReadDeviceInfoBeforeWrite() {
|
||||
guard let commandChar = characteristics[BeaconProvisioner.DXSMART_COMMAND_CHAR] else {
|
||||
DebugLog.shared.log("BLE: FFE2 not found, proceeding without MAC")
|
||||
dxSmartWriteConfig()
|
||||
return
|
||||
}
|
||||
|
||||
progress = "Reading device info..."
|
||||
awaitingDeviceInfoForProvisioning = true
|
||||
responseBuffer.removeAll()
|
||||
|
||||
// Send device info query (0x30)
|
||||
let packet = buildDXPacket(cmd: .deviceInfo, data: [])
|
||||
DebugLog.shared.log("BLE: Sending device info query to get MAC address")
|
||||
peripheral?.writeValue(packet, for: commandChar, type: .withResponse)
|
||||
|
||||
// Timeout after 3 seconds - proceed with write even if no MAC
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { [weak self] in
|
||||
guard let self = self, self.awaitingDeviceInfoForProvisioning else { return }
|
||||
DebugLog.shared.log("BLE: Device info timeout, proceeding without MAC")
|
||||
self.awaitingDeviceInfoForProvisioning = false
|
||||
self.dxSmartWriteConfig()
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the full command sequence and start writing
|
||||
/// New 24-step write sequence for DX-Smart CP28:
|
||||
/// 1. DeviceName 0x71 [name bytes] — service point name (max 20 ASCII chars)
|
||||
|
|
@ -1031,7 +1064,8 @@ extension BeaconProvisioner: CBPeripheralDelegate {
|
|||
if operationMode == .readingConfig {
|
||||
dxSmartReadQueryAfterAuth()
|
||||
} else {
|
||||
dxSmartWriteConfig()
|
||||
// Read device info first to get MAC address, then write config
|
||||
dxSmartReadDeviceInfoBeforeWrite()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -1043,6 +1077,9 @@ extension BeaconProvisioner: CBPeripheralDelegate {
|
|||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) { [weak self] in
|
||||
self?.dxSmartSendNextReadQuery()
|
||||
}
|
||||
} else if awaitingDeviceInfoForProvisioning {
|
||||
// Device info query was sent - wait for response on FFE1, don't process as normal command
|
||||
DebugLog.shared.log("BLE: Device info query sent, waiting for response...")
|
||||
} else {
|
||||
dxSmartWriteIndex += 1
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in
|
||||
|
|
@ -1090,11 +1127,55 @@ extension BeaconProvisioner: CBPeripheralDelegate {
|
|||
DebugLog.shared.log("BLE: Read \(characteristic.uuid): \(hex)")
|
||||
}
|
||||
} else {
|
||||
// Provisioning mode — just log FFE1 notifications
|
||||
// Provisioning mode
|
||||
if characteristic.uuid == BeaconProvisioner.DXSMART_NOTIFY_CHAR {
|
||||
let hex = data.map { String(format: "%02X", $0) }.joined(separator: " ")
|
||||
DebugLog.shared.log("BLE: FFE1 notification: \(hex)")
|
||||
|
||||
// If awaiting device info for MAC address, process the response
|
||||
if awaitingDeviceInfoForProvisioning {
|
||||
processDeviceInfoForProvisioning(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Process device info response during provisioning to extract MAC address
|
||||
private func processDeviceInfoForProvisioning(_ data: Data) {
|
||||
responseBuffer.append(contentsOf: data)
|
||||
|
||||
// Look for complete frame: 4E 4F 30 LEN DATA XOR
|
||||
guard responseBuffer.count >= 5 else { return }
|
||||
|
||||
// Find header
|
||||
guard let headerIdx = findDXHeader() else {
|
||||
responseBuffer.removeAll()
|
||||
return
|
||||
}
|
||||
|
||||
if headerIdx > 0 {
|
||||
responseBuffer.removeFirst(headerIdx)
|
||||
}
|
||||
|
||||
guard responseBuffer.count >= 5 else { return }
|
||||
|
||||
let cmd = responseBuffer[2]
|
||||
let len = Int(responseBuffer[3])
|
||||
let frameLen = 4 + len + 1
|
||||
|
||||
guard responseBuffer.count >= frameLen else { return }
|
||||
|
||||
// Check if this is device info response (0x30)
|
||||
if cmd == DXCmd.deviceInfo.rawValue && len >= 7 {
|
||||
// Parse MAC address from bytes 1-6 (byte 0 is battery)
|
||||
let macBytes = Array(responseBuffer[5..<11])
|
||||
provisioningMacAddress = macBytes.map { String(format: "%02X", $0) }.joined(separator: ":")
|
||||
DebugLog.shared.log("BLE: Got MAC address for provisioning: \(provisioningMacAddress ?? "nil")")
|
||||
}
|
||||
|
||||
// Clear buffer and proceed to write config
|
||||
responseBuffer.removeAll()
|
||||
awaitingDeviceInfoForProvisioning = false
|
||||
dxSmartWriteConfig()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,25 +98,7 @@ struct ScanView: View {
|
|||
// Beacon lists
|
||||
ScrollView {
|
||||
LazyVStack(spacing: 8) {
|
||||
// Detected iBeacons section (shows ownership status)
|
||||
if !detectedIBeacons.isEmpty {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("Detected Beacons")
|
||||
.font(.caption.weight(.semibold))
|
||||
.foregroundColor(.secondary)
|
||||
.padding(.horizontal)
|
||||
|
||||
ForEach(detectedIBeacons, id: \.minor) { ibeacon in
|
||||
iBeaconRow(ibeacon)
|
||||
}
|
||||
}
|
||||
.padding(.top, 8)
|
||||
|
||||
Divider()
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
|
||||
// BLE devices section (for provisioning)
|
||||
// BLE devices section (for provisioning) - shown first
|
||||
if !bleScanner.discoveredBeacons.isEmpty {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("Configurable Devices")
|
||||
|
|
@ -132,6 +114,12 @@ struct ScanView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.padding(.top, 8)
|
||||
|
||||
if !detectedIBeacons.isEmpty {
|
||||
Divider()
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
} else if bleScanner.isScanning {
|
||||
VStack(spacing: 12) {
|
||||
ProgressView()
|
||||
|
|
@ -155,6 +143,20 @@ struct ScanView: View {
|
|||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 40)
|
||||
}
|
||||
|
||||
// Detected iBeacons section (shows ownership status)
|
||||
if !detectedIBeacons.isEmpty {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("Detected Beacons")
|
||||
.font(.caption.weight(.semibold))
|
||||
.foregroundColor(.secondary)
|
||||
.padding(.horizontal)
|
||||
|
||||
ForEach(detectedIBeacons, id: \.minor) { ibeacon in
|
||||
iBeaconRow(ibeacon)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
|
|
@ -887,21 +889,23 @@ struct ScanView: View {
|
|||
|
||||
provisioningProgress = "Provisioning beacon..."
|
||||
|
||||
let hardwareId = beacon.id.uuidString // BLE peripheral identifier
|
||||
provisioner.provision(beacon: beacon, config: beaconConfig) { result in
|
||||
Task { @MainActor in
|
||||
switch result {
|
||||
case .success:
|
||||
case .success(let macAddress):
|
||||
do {
|
||||
// Register with the UUID format expected by API (with dashes)
|
||||
// Use MAC address as hardware ID, fallback to iBeacon UUID if unavailable
|
||||
let uuidWithDashes = formatUuidWithDashes(config.uuid)
|
||||
let hardwareId = macAddress ?? uuidWithDashes
|
||||
DebugLog.shared.log("[ScanView] Registering beacon - MAC: \(macAddress ?? "nil"), hardwareId: \(hardwareId), uuid: \(uuidWithDashes)")
|
||||
try await Api.shared.registerBeaconHardware(
|
||||
businessId: businessId,
|
||||
servicePointId: sp.servicePointId,
|
||||
uuid: uuidWithDashes,
|
||||
major: config.major,
|
||||
minor: config.minor,
|
||||
hardwareId: hardwareId
|
||||
hardwareId: hardwareId,
|
||||
macAddress: macAddress
|
||||
)
|
||||
finishProvisioning(name: sp.name)
|
||||
} catch {
|
||||
|
|
@ -967,21 +971,24 @@ struct ScanView: View {
|
|||
provisioningProgress = "Provisioning beacon..."
|
||||
|
||||
// 4. Provision the beacon via GATT
|
||||
let hardwareId = beacon.id.uuidString // BLE peripheral identifier
|
||||
provisioner.provision(beacon: beacon, config: beaconConfig) { result in
|
||||
Task { @MainActor in
|
||||
switch result {
|
||||
case .success:
|
||||
case .success(let macAddress):
|
||||
// Register in backend (use UUID with dashes for API)
|
||||
do {
|
||||
// Use MAC address as hardware ID, fallback to iBeacon UUID if unavailable
|
||||
let uuidWithDashes = formatUuidWithDashes(config.uuid)
|
||||
let hardwareId = macAddress ?? uuidWithDashes
|
||||
DebugLog.shared.log("[ScanView] Registering beacon - MAC: \(macAddress ?? "nil"), hardwareId: \(hardwareId), uuid: \(uuidWithDashes)")
|
||||
try await Api.shared.registerBeaconHardware(
|
||||
businessId: businessId,
|
||||
servicePointId: servicePoint.servicePointId,
|
||||
uuid: uuidWithDashes,
|
||||
major: config.major,
|
||||
minor: config.minor,
|
||||
hardwareId: hardwareId
|
||||
hardwareId: hardwareId,
|
||||
macAddress: macAddress
|
||||
)
|
||||
finishProvisioning(name: name)
|
||||
} catch {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue