This commit is contained in:
2025-11-27 18:15:24 +09:00
parent 9384584618
commit 7f6e35db6b
4 changed files with 184 additions and 7 deletions

View File

@@ -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<String> = []
private var centralManager: CBCentralManager!
private var seenNonces: Set<String> = []
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<String> = []
// 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")

View File

@@ -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)
}
}
}

View File

@@ -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()
}
}
}
}
}
}