From 3c2c5629831028dfb750bb900bc692fbfa2c0b09 Mon Sep 17 00:00:00 2001 From: Simon Oberzier Date: Fri, 20 Feb 2026 20:50:24 +0100 Subject: [PATCH] Initial commit --- .gitignore | 1 + mcrl-app.xcodeproj/project.pbxproj | 354 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../UserInterfaceState.xcuserstate | Bin 0 -> 30318 bytes .../xcdebugger/Breakpoints_v2.xcbkptlist | 6 + .../xcschemes/xcschememanagement.plist | 14 + .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 35 ++ mcrl-app/Assets.xcassets/Contents.json | 6 + mcrl-app/BluetoothManager.swift | 115 ++++++ mcrl-app/ContentView.swift | 132 +++++++ mcrl-app/Info.plist | 10 + mcrl-app/SensorData.swift | 17 + mcrl-app/mcrl_appApp.swift | 17 + 14 files changed, 725 insertions(+) create mode 100644 .gitignore create mode 100644 mcrl-app.xcodeproj/project.pbxproj create mode 100644 mcrl-app.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 mcrl-app.xcodeproj/project.xcworkspace/xcuserdata/simon.xcuserdatad/UserInterfaceState.xcuserstate create mode 100644 mcrl-app.xcodeproj/xcuserdata/simon.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist create mode 100644 mcrl-app.xcodeproj/xcuserdata/simon.xcuserdatad/xcschemes/xcschememanagement.plist create mode 100644 mcrl-app/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 mcrl-app/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 mcrl-app/Assets.xcassets/Contents.json create mode 100644 mcrl-app/BluetoothManager.swift create mode 100644 mcrl-app/ContentView.swift create mode 100644 mcrl-app/Info.plist create mode 100644 mcrl-app/SensorData.swift create mode 100644 mcrl-app/mcrl_appApp.swift 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 0000000000000000000000000000000000000000..af7d05216e2b2be4bbfa4114a84ffcdcabb8cc61 GIT binary patch literal 30318 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 literal 0 HcmV?d00001 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() + } + } +}