commit 3c2c5629831028dfb750bb900bc692fbfa2c0b09 Author: Simon Oberzier Date: Fri Feb 20 20:50:24 2026 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/mcrl-app.xcodeproj/project.pbxproj b/mcrl-app.xcodeproj/project.pbxproj new file mode 100644 index 0000000..0d05e39 --- /dev/null +++ b/mcrl-app.xcodeproj/project.pbxproj @@ -0,0 +1,354 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXFileReference section */ + F494E8782F162CF8006B7586 /* mcrl-app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "mcrl-app.app"; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + F4ABF3B02F44DE16000D4D1B /* Exceptions for "mcrl-app" folder in "mcrl-app" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + ); + target = F494E8772F162CF8006B7586 /* mcrl-app */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + F494E87A2F162CF8006B7586 /* mcrl-app */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + F4ABF3B02F44DE16000D4D1B /* Exceptions for "mcrl-app" folder in "mcrl-app" target */, + ); + path = "mcrl-app"; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + F494E8752F162CF8006B7586 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + F494E86F2F162CF8006B7586 = { + isa = PBXGroup; + children = ( + F494E87A2F162CF8006B7586 /* mcrl-app */, + F494E8792F162CF8006B7586 /* Products */, + ); + sourceTree = ""; + }; + F494E8792F162CF8006B7586 /* Products */ = { + isa = PBXGroup; + children = ( + F494E8782F162CF8006B7586 /* mcrl-app.app */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + F494E8772F162CF8006B7586 /* mcrl-app */ = { + isa = PBXNativeTarget; + buildConfigurationList = F494E8832F162CF9006B7586 /* Build configuration list for PBXNativeTarget "mcrl-app" */; + buildPhases = ( + F494E8742F162CF8006B7586 /* Sources */, + F494E8752F162CF8006B7586 /* Frameworks */, + F494E8762F162CF8006B7586 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + F494E87A2F162CF8006B7586 /* mcrl-app */, + ); + name = "mcrl-app"; + packageProductDependencies = ( + ); + productName = "mcrl-app"; + productReference = F494E8782F162CF8006B7586 /* mcrl-app.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + F494E8702F162CF8006B7586 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 2610; + LastUpgradeCheck = 2610; + TargetAttributes = { + F494E8772F162CF8006B7586 = { + CreatedOnToolsVersion = 26.1.1; + }; + }; + }; + buildConfigurationList = F494E8732F162CF8006B7586 /* Build configuration list for PBXProject "mcrl-app" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = F494E86F2F162CF8006B7586; + minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 77; + productRefGroup = F494E8792F162CF8006B7586 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + F494E8772F162CF8006B7586 /* mcrl-app */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + F494E8762F162CF8006B7586 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + F494E8742F162CF8006B7586 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + F494E8812F162CF9006B7586 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 26.1; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + F494E8822F162CF9006B7586 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 26.1; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + F494E8842F162CF9006B7586 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = KH9SLY4BHX; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "mcrl-app/Info.plist"; + INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "This app needs Bluetooth to connect to your Pico 2 and display sensor data."; + INFOPLIST_KEY_NSBluetoothPeripheralUsageDescription = "This app needs Bluetooth to connect to your Pico 2 and display sensor data."; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "dev.asedem.mcrl-app"; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + F494E8852F162CF9006B7586 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = KH9SLY4BHX; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "mcrl-app/Info.plist"; + INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "This app needs Bluetooth to connect to your Pico 2 and display sensor data."; + INFOPLIST_KEY_NSBluetoothPeripheralUsageDescription = "This app needs Bluetooth to connect to your Pico 2 and display sensor data."; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "dev.asedem.mcrl-app"; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + F494E8732F162CF8006B7586 /* Build configuration list for PBXProject "mcrl-app" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F494E8812F162CF9006B7586 /* Debug */, + F494E8822F162CF9006B7586 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F494E8832F162CF9006B7586 /* Build configuration list for PBXNativeTarget "mcrl-app" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F494E8842F162CF9006B7586 /* Debug */, + F494E8852F162CF9006B7586 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = F494E8702F162CF8006B7586 /* Project object */; +} diff --git a/mcrl-app.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/mcrl-app.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/mcrl-app.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/mcrl-app.xcodeproj/project.xcworkspace/xcuserdata/simon.xcuserdatad/UserInterfaceState.xcuserstate b/mcrl-app.xcodeproj/project.xcworkspace/xcuserdata/simon.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..af7d052 Binary files /dev/null and b/mcrl-app.xcodeproj/project.xcworkspace/xcuserdata/simon.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/mcrl-app.xcodeproj/xcuserdata/simon.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/mcrl-app.xcodeproj/xcuserdata/simon.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..7413485 --- /dev/null +++ b/mcrl-app.xcodeproj/xcuserdata/simon.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,6 @@ + + + diff --git a/mcrl-app.xcodeproj/xcuserdata/simon.xcuserdatad/xcschemes/xcschememanagement.plist b/mcrl-app.xcodeproj/xcuserdata/simon.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..5fcd2b8 --- /dev/null +++ b/mcrl-app.xcodeproj/xcuserdata/simon.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + mcrl-app.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/mcrl-app/Assets.xcassets/AccentColor.colorset/Contents.json b/mcrl-app/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/mcrl-app/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/mcrl-app/Assets.xcassets/AppIcon.appiconset/Contents.json b/mcrl-app/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..2305880 --- /dev/null +++ b/mcrl-app/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,35 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/mcrl-app/Assets.xcassets/Contents.json b/mcrl-app/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/mcrl-app/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/mcrl-app/BluetoothManager.swift b/mcrl-app/BluetoothManager.swift new file mode 100644 index 0000000..766885f --- /dev/null +++ b/mcrl-app/BluetoothManager.swift @@ -0,0 +1,115 @@ +// +// BluetoothManager.swift +// mcrl-app +// +// Created by Simon Oberzier on 17.02.26. +// + +import CoreBluetooth +import Combine + +class BluetoothManager: NSObject, ObservableObject { + @Published var sensorValues = SensorData() + @Published var connectionStatus = "Disconnected" + + var centralManager: CBCentralManager! + var picoPeripheral: CBPeripheral? + + let serviceUUID = CBUUID(string: "0000FF00-0000-1000-8000-00805F9B34FB") + let charUUID = CBUUID(string: "0000FF01-0000-1000-8000-00805F9B34FB") + + override init() { + super.init() + centralManager = CBCentralManager(delegate: self, queue: nil) + } + + func parseSensorString(_ str: String) { + let cleanStr = str.trimmingCharacters(in: .whitespacesAndNewlines) + let components = cleanStr.components(separatedBy: ",") + + guard components.count >= 7, components[0] == "S" else { + print("Received malformed string: \(cleanStr)") + return + } + + DispatchQueue.main.async { + self.sensorValues.ax = Double(components[1]) ?? 0 + self.sensorValues.ay = Double(components[2]) ?? 0 + self.sensorValues.az = Double(components[3]) ?? 0 + self.sensorValues.lat = Double(components[4]) ?? 0 + self.sensorValues.lon = Double(components[5]) ?? 0 + self.sensorValues.hasFix = components[6].contains("1") + } + } +} + +extension BluetoothManager: CBCentralManagerDelegate { + func centralManagerDidUpdateState(_ central: CBCentralManager) { + switch central.state { + case .poweredOn: + connectionStatus = "Scanning..." + // Scanning with nil finds EVERYTHING. Safer for debugging. + centralManager.scanForPeripherals(withServices: nil, options: nil) + case .poweredOff: + connectionStatus = "Bluetooth is Off" + case .unauthorized: + connectionStatus = "Permission Denied" + default: + connectionStatus = "Unknown State" + } + } + + func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) { + let name = peripheral.name ?? "Unknown" + + if name.contains("Pico") { + print("Discovered: \(name)") + connectionStatus = "Pico Found!" + + picoPeripheral = peripheral + picoPeripheral?.delegate = self + + centralManager.stopScan() + centralManager.connect(peripheral, options: nil) + } + } + + func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { + connectionStatus = "Connected" + print("Connected to \(peripheral.name ?? "Device")") + peripheral.discoverServices([serviceUUID]) + } + + func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) { + connectionStatus = "Disconnected" + print("Disconnected. Restarting scan...") + centralManager.scanForPeripherals(withServices: nil, options: nil) + } +} + +extension BluetoothManager: CBPeripheralDelegate { + func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { + guard let services = peripheral.services else { return } + for service in services { + print("Service discovered: \(service.uuid)") + peripheral.discoverCharacteristics([charUUID], for: service) + } + } + + func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { + guard let characteristics = service.characteristics else { return } + for characteristic in characteristics { + if characteristic.uuid == charUUID { + print("Found Data Characteristic!") + peripheral.setNotifyValue(true, for: characteristic) + } + } + } + + func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { + if let data = characteristic.value, + let str = String(data: data, encoding: .utf8) { + parseSensorString(str) + } + } +} diff --git a/mcrl-app/ContentView.swift b/mcrl-app/ContentView.swift new file mode 100644 index 0000000..ae0ebd1 --- /dev/null +++ b/mcrl-app/ContentView.swift @@ -0,0 +1,132 @@ +// +// ContentView.swift +// mcrl-app +// +// Created by Simon Oberzier on 13.01.26. +// + +import SwiftUI + +struct ContentView: View { + @StateObject var bleManager = BluetoothManager() + + var experiencedG: Double { + let totalVector = sqrt( + pow(bleManager.sensorValues.ax, 2) + + pow(bleManager.sensorValues.ay, 2) + + pow(bleManager.sensorValues.az, 2) + ) + return max(0, totalVector - 1.0) + } + + var body: some View { + NavigationView { + VStack(spacing: 20) { + HStack { + Label(bleManager.connectionStatus.uppercased(), systemImage: bleManager.connectionStatus == "Connected" ? "bolt.fill" : "bolt.slash.fill") + .font(.system(size: 10, weight: .black, design: .monospaced)) + .foregroundColor(bleManager.connectionStatus == "Connected" ? .green : .red) + Spacer() + Text("LIVE TELEMETRY") + .font(.system(size: 10, weight: .bold)) + .foregroundColor(.secondary) + } + .padding(.horizontal) + + ZStack { + Circle() + .trim(from: 0.1, to: 0.9) + .stroke(Color.primary.opacity(0.1), style: StrokeStyle(lineWidth: 30, lineCap: .round)) + .rotationEffect(.degrees(90)) + + Circle() + .trim(from: 0.1, to: min(0.1 + (experiencedG / 3.0) * 0.8, 0.9)) // Scaled for 0-3G range + .stroke( + AngularGradient( + gradient: Gradient(colors: [.green, .yellow, .orange, .red]), + center: .center, + angle: .degrees(180) + ), + style: StrokeStyle(lineWidth: 30, lineCap: .round) + ) + .rotationEffect(.degrees(90)) + .animation(.interactiveSpring(response: 0.2, dampingFraction: 0.5), value: experiencedG) + + VStack(spacing: -2) { + Text(String(format: "%.2f", experiencedG)) + .font(.system(size: 64, weight: .black, design: .rounded)) + Text("NET G-FORCE") + .font(.system(size: 12, weight: .heavy)) + .foregroundColor(.secondary) + } + } + .frame(width: 280, height: 280) + .padding(.vertical) + + LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 15) { + DataTile(title: "LATERAL X", value: bleManager.sensorValues.ax, unit: "G", color: .blue) + DataTile(title: "LONG Y", value: bleManager.sensorValues.ay, unit: "G", color: .orange) + DataTile(title: "VERTICAL Z", value: bleManager.sensorValues.az, unit: "G", color: .purple) + DataTile(title: "GPS STATUS", value: bleManager.sensorValues.hasFix ? 1.0 : 0.0, unit: bleManager.sensorValues.hasFix ? "FIXED" : "NO FIX", color: .green) + } + .padding(.horizontal) + + if bleManager.sensorValues.hasFix { + HStack { + VStack(alignment: .leading) { + Text("COORDINATES").font(.system(size: 9, weight: .black)).foregroundColor(.secondary) + Text("\(bleManager.sensorValues.lat, specifier: "%.5f"), \(bleManager.sensorValues.lon, specifier: "%.5f")") + .font(.system(.caption, design: .monospaced)) + .fontWeight(.bold) + } + Spacer() + Image(systemName: "location.north.circle.fill") + .foregroundColor(.blue) + } + .padding() + .background(Color.primary.opacity(0.05)) + .cornerRadius(12) + .padding(.horizontal) + } + + Spacer() + } + .navigationTitle("Pico Link") + .navigationBarTitleDisplayMode(.inline) + } + } +} + +struct DataTile: View { + let title: String + let value: Double + let unit: String + let color: Color + + var body: some View { + VStack(alignment: .leading, spacing: 4) { + Text(title) + .font(.system(size: 9, weight: .black)) + .foregroundColor(color) + + HStack(alignment: .lastTextBaseline, spacing: 2) { + if unit == "G" { + Text(String(format: "%.2f", value)) + .font(.system(.title2, design: .monospaced)) + .fontWeight(.bold) + } + Text(unit) + .font(.system(size: 10, weight: .bold)) + .foregroundColor(.secondary) + } + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding() + .background(Color.primary.opacity(0.05)) + .cornerRadius(12) + .overlay( + RoundedRectangle(cornerRadius: 12) + .stroke(color.opacity(0.2), lineWidth: 1) + ) + } +} diff --git a/mcrl-app/Info.plist b/mcrl-app/Info.plist new file mode 100644 index 0000000..e999718 --- /dev/null +++ b/mcrl-app/Info.plist @@ -0,0 +1,10 @@ + + + + + UIBackgroundModes + + bluetooth-central + + + diff --git a/mcrl-app/SensorData.swift b/mcrl-app/SensorData.swift new file mode 100644 index 0000000..4ebc0ad --- /dev/null +++ b/mcrl-app/SensorData.swift @@ -0,0 +1,17 @@ +// +// SensorData.swift +// mcrl-app +// +// Created by Simon Oberzier on 17.02.26. +// + +import Foundation + +struct SensorData { + var ax: Double = 0.0 + var ay: Double = 0.0 + var az: Double = 0.0 + var lat: Double = 0.0 + var lon: Double = 0.0 + var hasFix: Bool = false +} diff --git a/mcrl-app/mcrl_appApp.swift b/mcrl-app/mcrl_appApp.swift new file mode 100644 index 0000000..79a756f --- /dev/null +++ b/mcrl-app/mcrl_appApp.swift @@ -0,0 +1,17 @@ +// +// mcrl_appApp.swift +// mcrl-app +// +// Created by Simon Oberzier on 13.01.26. +// + +import SwiftUI + +@main +struct mcrl_appApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +}