From 7f6e35db6b6f4c2fde2a5a82674a2c3c53021295 Mon Sep 17 00:00:00 2001 From: Morgan Date: Thu, 27 Nov 2025 18:15:24 +0900 Subject: [PATCH] . --- EnvSensorReader.xcodeproj/project.pbxproj | 4 + EnvSensorReader/BLEManager.swift | 40 +++++-- EnvSensorReader/ContentView.swift | 25 +++++ EnvSensorReader/SettingsView.swift | 122 ++++++++++++++++++++++ 4 files changed, 184 insertions(+), 7 deletions(-) create mode 100644 EnvSensorReader/SettingsView.swift diff --git a/EnvSensorReader.xcodeproj/project.pbxproj b/EnvSensorReader.xcodeproj/project.pbxproj index 3b070fb..13365a7 100644 --- a/EnvSensorReader.xcodeproj/project.pbxproj +++ b/EnvSensorReader.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ AA0000000000000000000003 /* BLEManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB0000000000000000000003 /* BLEManager.swift */; }; AA0000000000000000000004 /* SensorReading.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB0000000000000000000004 /* SensorReading.swift */; }; AA0000000000000000000005 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BB0000000000000000000006 /* Assets.xcassets */; }; + AA0000000000000000000007 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB0000000000000000000007 /* SettingsView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -21,6 +22,7 @@ BB0000000000000000000004 /* SensorReading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SensorReading.swift; sourceTree = ""; }; BB0000000000000000000005 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; BB0000000000000000000006 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + BB0000000000000000000007 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; CC0000000000000000000001 /* EnvSensorReader.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = EnvSensorReader.app; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -48,6 +50,7 @@ children = ( BB0000000000000000000001 /* EnvSensorReaderApp.swift */, BB0000000000000000000002 /* ContentView.swift */, + BB0000000000000000000007 /* SettingsView.swift */, BB0000000000000000000003 /* BLEManager.swift */, BB0000000000000000000004 /* SensorReading.swift */, BB0000000000000000000006 /* Assets.xcassets */, @@ -135,6 +138,7 @@ files = ( AA0000000000000000000001 /* EnvSensorReaderApp.swift in Sources */, AA0000000000000000000002 /* ContentView.swift in Sources */, + AA0000000000000000000007 /* SettingsView.swift in Sources */, AA0000000000000000000003 /* BLEManager.swift in Sources */, AA0000000000000000000004 /* SensorReading.swift in Sources */, ); diff --git a/EnvSensorReader/BLEManager.swift b/EnvSensorReader/BLEManager.swift index 5e8fd3d..5ddfa71 100644 --- a/EnvSensorReader/BLEManager.swift +++ b/EnvSensorReader/BLEManager.swift @@ -6,13 +6,21 @@ class BLEManager: NSObject, ObservableObject { @Published var readings: [SensorReading] = [] @Published var isScanning = false @Published var bluetoothState: CBManagerState = .unknown + @Published var discoveredDevices: Set = [] private var centralManager: CBCentralManager! private var seenNonces: Set = [] - private let deviceName = "EnvSensor" + // Configuration private let companyID: UInt16 = 0xFFFF + // Optional: Set specific UUIDs to filter. Empty = accept all devices with correct company ID + // Example: ["12345678-1234-1234-1234-123456789ABC"] + var allowedUUIDs: Set = [] + + // Set to true to show all devices regardless of company ID (for discovery) + var discoveryMode = false + override init() { super.init() centralManager = CBCentralManager(delegate: self, queue: nil) @@ -88,14 +96,15 @@ extension BLEManager: CBCentralManagerDelegate { } func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) { - // Check device name - guard let name = peripheral.name, name == deviceName else { - return - } + let deviceUUID = peripheral.identifier.uuidString // Check for manufacturer data guard let manufacturerData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data, manufacturerData.count >= 2 else { + // In discovery mode, log devices without manufacturer data + if discoveryMode { + print("Device without manufacturer data: \(deviceUUID) (\(peripheral.name ?? "Unknown"))") + } return } @@ -103,10 +112,26 @@ extension BLEManager: CBCentralManagerDelegate { let companyIDBytes = manufacturerData.prefix(2) let extractedCompanyID = UInt16(companyIDBytes[0]) | (UInt16(companyIDBytes[1]) << 8) + // In discovery mode, log all devices with their company IDs + if discoveryMode { + print("Discovered: \(deviceUUID) (\(peripheral.name ?? "Unknown")) - Company ID: 0x\(String(format: "%04X", extractedCompanyID))") + } + + // Filter by company ID guard extractedCompanyID == companyID else { return } + // Add to discovered devices + DispatchQueue.main.async { + self.discoveredDevices.insert(deviceUUID) + } + + // Filter by UUID whitelist if configured + if !allowedUUIDs.isEmpty && !allowedUUIDs.contains(deviceUUID) { + return + } + // Parse the payload (skip the first 2 bytes which are the company ID) let payload = manufacturerData.dropFirst(2) @@ -115,7 +140,7 @@ extension BLEManager: CBCentralManagerDelegate { } // Create unique key for deduplication - let key = "\(peripheral.identifier.uuidString)-\(parsed.nonce)" + let key = "\(deviceUUID)-\(parsed.nonce)" guard !seenNonces.contains(key) else { return } @@ -143,7 +168,8 @@ extension BLEManager: CBCentralManagerDelegate { } } - print("[\(reading.timestampString)] (\(String(format: "%04X", parsed.nonce))) \(peripheral.identifier.uuidString)") + let deviceName = peripheral.name ?? "Unknown" + print("[\(reading.timestampString)] (\(String(format: "%04X", parsed.nonce))) \(deviceUUID) (\(deviceName))") print(" T=\(String(format: "%5.1f", parsed.temp))°C H=\(String(format: "%5.1f", parsed.hum))% P=\(String(format: "%7.1f", parsed.pres))hPa") print(" V=\(String(format: "%5.2f", parsed.voltage))V I=\(String(format: "%7.2f", parsed.current))mA P=\(String(format: "%7.2f", reading.power))mW") print(" RSSI=\(String(format: "%3d", RSSI.intValue))dBm") diff --git a/EnvSensorReader/ContentView.swift b/EnvSensorReader/ContentView.swift index 8b23dd5..f28ced3 100644 --- a/EnvSensorReader/ContentView.swift +++ b/EnvSensorReader/ContentView.swift @@ -2,6 +2,7 @@ import SwiftUI struct ContentView: View { @StateObject private var bleManager = BLEManager() + @State private var showSettings = false var body: some View { NavigationView { @@ -64,6 +65,30 @@ struct ContentView: View { } .navigationTitle("EnvSensor Reader") .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button(action: { + showSettings = true + }) { + Image(systemName: "gearshape") + } + } + + ToolbarItem(placement: .navigationBarLeading) { + if !bleManager.discoveredDevices.isEmpty { + HStack(spacing: 4) { + Image(systemName: "antenna.radiowaves.left.and.right") + .font(.caption) + Text("\(bleManager.discoveredDevices.count)") + .font(.caption) + .foregroundColor(.secondary) + } + } + } + } + .sheet(isPresented: $showSettings) { + SettingsView(bleManager: bleManager) + } } } diff --git a/EnvSensorReader/SettingsView.swift b/EnvSensorReader/SettingsView.swift new file mode 100644 index 0000000..d5c7a1f --- /dev/null +++ b/EnvSensorReader/SettingsView.swift @@ -0,0 +1,122 @@ +import SwiftUI + +struct SettingsView: View { + @ObservedObject var bleManager: BLEManager + @State private var newUUID = "" + @Environment(\.dismiss) var dismiss + + var body: some View { + NavigationView { + Form { + Section(header: Text("Discovered Devices")) { + if bleManager.discoveredDevices.isEmpty { + Text("No devices discovered yet") + .foregroundColor(.secondary) + .italic() + } else { + ForEach(Array(bleManager.discoveredDevices).sorted(), id: \.self) { uuid in + HStack { + VStack(alignment: .leading) { + Text(uuid) + .font(.system(.caption, design: .monospaced)) + Text(bleManager.allowedUUIDs.contains(uuid) ? "Allowed" : "Not filtered") + .font(.caption2) + .foregroundColor(bleManager.allowedUUIDs.contains(uuid) ? .green : .secondary) + } + Spacer() + if bleManager.allowedUUIDs.contains(uuid) { + Button("Remove") { + bleManager.allowedUUIDs.remove(uuid) + } + .foregroundColor(.red) + } else { + Button("Add") { + bleManager.allowedUUIDs.insert(uuid) + } + } + } + } + } + } + + Section(header: Text("Device Filter")) { + Toggle("Filter by UUID", isOn: Binding( + get: { !bleManager.allowedUUIDs.isEmpty }, + set: { enabled in + if !enabled { + bleManager.allowedUUIDs.removeAll() + } + } + )) + + if !bleManager.allowedUUIDs.isEmpty { + Text("Only devices with these UUIDs will be shown") + .font(.caption) + .foregroundColor(.secondary) + + ForEach(Array(bleManager.allowedUUIDs).sorted(), id: \.self) { uuid in + HStack { + Text(uuid) + .font(.system(.caption, design: .monospaced)) + Spacer() + Button(action: { + bleManager.allowedUUIDs.remove(uuid) + }) { + Image(systemName: "xmark.circle.fill") + .foregroundColor(.red) + } + } + } + } else { + Text("All devices with Company ID 0xFFFF will be shown") + .font(.caption) + .foregroundColor(.secondary) + } + } + + Section(header: Text("Manual UUID Entry")) { + HStack { + TextField("Enter UUID", text: $newUUID) + .autocapitalization(.allCharacters) + .font(.system(.body, design: .monospaced)) + Button("Add") { + let trimmed = newUUID.trimmingCharacters(in: .whitespacesAndNewlines).uppercased() + if !trimmed.isEmpty { + bleManager.allowedUUIDs.insert(trimmed) + newUUID = "" + } + } + .disabled(newUUID.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) + } + Text("Format: 12345678-1234-1234-1234-123456789ABC") + .font(.caption2) + .foregroundColor(.secondary) + } + + Section(header: Text("Advanced")) { + Toggle("Discovery Mode", isOn: $bleManager.discoveryMode) + Text("Shows all BLE devices in console logs") + .font(.caption) + .foregroundColor(.secondary) + } + + Section { + Button("Clear All Filters") { + bleManager.allowedUUIDs.removeAll() + } + .foregroundColor(.red) + .disabled(bleManager.allowedUUIDs.isEmpty) + } + } + .navigationTitle("Settings") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button("Done") { + dismiss() + } + } + } + } + } +}