mirror of
https://github.com/morgan9e/SensorReader
synced 2026-04-14 16:37:20 +09:00
.
This commit is contained in:
@@ -12,6 +12,7 @@
|
|||||||
AA0000000000000000000003 /* BLEManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB0000000000000000000003 /* BLEManager.swift */; };
|
AA0000000000000000000003 /* BLEManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB0000000000000000000003 /* BLEManager.swift */; };
|
||||||
AA0000000000000000000004 /* SensorReading.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB0000000000000000000004 /* SensorReading.swift */; };
|
AA0000000000000000000004 /* SensorReading.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB0000000000000000000004 /* SensorReading.swift */; };
|
||||||
AA0000000000000000000005 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BB0000000000000000000006 /* Assets.xcassets */; };
|
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 */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
@@ -21,6 +22,7 @@
|
|||||||
BB0000000000000000000004 /* SensorReading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SensorReading.swift; sourceTree = "<group>"; };
|
BB0000000000000000000004 /* SensorReading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SensorReading.swift; sourceTree = "<group>"; };
|
||||||
BB0000000000000000000005 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
BB0000000000000000000005 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
BB0000000000000000000006 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
BB0000000000000000000006 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
|
BB0000000000000000000007 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
|
||||||
CC0000000000000000000001 /* EnvSensorReader.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = EnvSensorReader.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
CC0000000000000000000001 /* EnvSensorReader.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = EnvSensorReader.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
@@ -48,6 +50,7 @@
|
|||||||
children = (
|
children = (
|
||||||
BB0000000000000000000001 /* EnvSensorReaderApp.swift */,
|
BB0000000000000000000001 /* EnvSensorReaderApp.swift */,
|
||||||
BB0000000000000000000002 /* ContentView.swift */,
|
BB0000000000000000000002 /* ContentView.swift */,
|
||||||
|
BB0000000000000000000007 /* SettingsView.swift */,
|
||||||
BB0000000000000000000003 /* BLEManager.swift */,
|
BB0000000000000000000003 /* BLEManager.swift */,
|
||||||
BB0000000000000000000004 /* SensorReading.swift */,
|
BB0000000000000000000004 /* SensorReading.swift */,
|
||||||
BB0000000000000000000006 /* Assets.xcassets */,
|
BB0000000000000000000006 /* Assets.xcassets */,
|
||||||
@@ -135,6 +138,7 @@
|
|||||||
files = (
|
files = (
|
||||||
AA0000000000000000000001 /* EnvSensorReaderApp.swift in Sources */,
|
AA0000000000000000000001 /* EnvSensorReaderApp.swift in Sources */,
|
||||||
AA0000000000000000000002 /* ContentView.swift in Sources */,
|
AA0000000000000000000002 /* ContentView.swift in Sources */,
|
||||||
|
AA0000000000000000000007 /* SettingsView.swift in Sources */,
|
||||||
AA0000000000000000000003 /* BLEManager.swift in Sources */,
|
AA0000000000000000000003 /* BLEManager.swift in Sources */,
|
||||||
AA0000000000000000000004 /* SensorReading.swift in Sources */,
|
AA0000000000000000000004 /* SensorReading.swift in Sources */,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,13 +6,21 @@ class BLEManager: NSObject, ObservableObject {
|
|||||||
@Published var readings: [SensorReading] = []
|
@Published var readings: [SensorReading] = []
|
||||||
@Published var isScanning = false
|
@Published var isScanning = false
|
||||||
@Published var bluetoothState: CBManagerState = .unknown
|
@Published var bluetoothState: CBManagerState = .unknown
|
||||||
|
@Published var discoveredDevices: Set<String> = []
|
||||||
|
|
||||||
private var centralManager: CBCentralManager!
|
private var centralManager: CBCentralManager!
|
||||||
private var seenNonces: Set<String> = []
|
private var seenNonces: Set<String> = []
|
||||||
|
|
||||||
private let deviceName = "EnvSensor"
|
// Configuration
|
||||||
private let companyID: UInt16 = 0xFFFF
|
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() {
|
override init() {
|
||||||
super.init()
|
super.init()
|
||||||
centralManager = CBCentralManager(delegate: self, queue: nil)
|
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) {
|
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
|
||||||
// Check device name
|
let deviceUUID = peripheral.identifier.uuidString
|
||||||
guard let name = peripheral.name, name == deviceName else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for manufacturer data
|
// Check for manufacturer data
|
||||||
guard let manufacturerData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data,
|
guard let manufacturerData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data,
|
||||||
manufacturerData.count >= 2 else {
|
manufacturerData.count >= 2 else {
|
||||||
|
// In discovery mode, log devices without manufacturer data
|
||||||
|
if discoveryMode {
|
||||||
|
print("Device without manufacturer data: \(deviceUUID) (\(peripheral.name ?? "Unknown"))")
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,10 +112,26 @@ extension BLEManager: CBCentralManagerDelegate {
|
|||||||
let companyIDBytes = manufacturerData.prefix(2)
|
let companyIDBytes = manufacturerData.prefix(2)
|
||||||
let extractedCompanyID = UInt16(companyIDBytes[0]) | (UInt16(companyIDBytes[1]) << 8)
|
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 {
|
guard extractedCompanyID == companyID else {
|
||||||
return
|
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)
|
// Parse the payload (skip the first 2 bytes which are the company ID)
|
||||||
let payload = manufacturerData.dropFirst(2)
|
let payload = manufacturerData.dropFirst(2)
|
||||||
|
|
||||||
@@ -115,7 +140,7 @@ extension BLEManager: CBCentralManagerDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create unique key for deduplication
|
// Create unique key for deduplication
|
||||||
let key = "\(peripheral.identifier.uuidString)-\(parsed.nonce)"
|
let key = "\(deviceUUID)-\(parsed.nonce)"
|
||||||
guard !seenNonces.contains(key) else {
|
guard !seenNonces.contains(key) else {
|
||||||
return
|
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(" 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(" 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")
|
print(" RSSI=\(String(format: "%3d", RSSI.intValue))dBm")
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import SwiftUI
|
|||||||
|
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
@StateObject private var bleManager = BLEManager()
|
@StateObject private var bleManager = BLEManager()
|
||||||
|
@State private var showSettings = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
@@ -64,6 +65,30 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
.navigationTitle("EnvSensor Reader")
|
.navigationTitle("EnvSensor Reader")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
122
EnvSensorReader/SettingsView.swift
Normal file
122
EnvSensorReader/SettingsView.swift
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user