小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

Halo 正式開源: 使用可穿戴設(shè)備進(jìn)行開源健康追蹤

 新用戶62592529 2024-12-19
圖片

在飛速發(fā)展的可穿戴技術(shù)領(lǐng)域,我們正處于一個十字路口——市場上充斥著各式時尚、功能豐富的設(shè)備,聲稱能夠徹底改變我們對健康和健身的方式。

然而,在這些光鮮的外觀和營銷宣傳背后,隱藏著一個令人擔(dān)憂的現(xiàn)實:大多數(shù)這些設(shè)備是封閉系統(tǒng),其內(nèi)部運行被專有代碼和封閉硬件所掩蓋。作為消費者,我們對這些設(shè)備如何收集、處理及可能共享我們的健康數(shù)據(jù)一無所知。

這時,Halo 出現(xiàn)了,它是一種旨在讓健康追蹤更加普惠化的開源替代方案。通過這系列文章,我們將引導(dǎo)你從基礎(chǔ)入手,構(gòu)建并使用完全透明、可定制的可穿戴設(shè)備。

需要說明的是,Halo 的目標(biāo)并不是在外觀或功能完整性上與消費級可穿戴設(shè)備競爭。相反,它提供了一種獨特的、動手實踐的方式來理解健康追蹤設(shè)備背后的技術(shù)。

我們將使用 Swift 5 來構(gòu)建對應(yīng)的 iOS 界面,Python >= 3.10。由于此項目的代碼完全開源,你可以隨時提交 PR 拉取請求,或者 Fork 分叉項目以探索全新的方向。

  • 開源https://github.com/cyrilzakka/Halo-iOS

你將需要:

  • 獲取COLMI R02實體設(shè)備,價格在撰寫時為 11 到 30 美金左右。https://www./item/3256806445134241.html?gatewayAdapt=glo2usa4itemAdapt
  • 一個安裝了 Xcode 16 的開發(fā)環(huán)境,以及可選的 Apple 開發(fā)者計劃會員資格。
  • Python >= 3.10,并安裝了 pandasnumpytorch 當(dāng)然還有 transformers

致謝

此項目基于Python 倉庫的代碼及我的學(xué)習(xí)成果構(gòu)建。

  • Python 倉庫https://tahnok./colmi_r02_client/

免責(zé)聲明

作為一名醫(yī)生,我有法律義務(wù)提醒你:你即將閱讀的內(nèi)容并不是醫(yī)學(xué)建議。現(xiàn)在,讓我們開始讓一些可穿戴設(shè)備發(fā)出蜂鳴聲吧!

配對戒指

在進(jìn)入代碼之前,讓我們先了解藍(lán)牙低能耗 (BLE) 的關(guān)鍵規(guī)格。BLE 基于一個簡單的客戶端-服務(wù)器模型,使用三個核心概念:中央設(shè)備 (Centrals)服務(wù) (Services) 和 **特征 (Characteristics)**。以下是它們的具體介紹:

  • 中央設(shè)備 (例如你的 iPhone) 負(fù)責(zé)啟動和管理與外設(shè) (例如我們的 COLMI R02 戒指) 的連接。戒指通過廣播自身信息等待手機(jī)連接,每次僅支持一臺手機(jī)連接。
  • 服務(wù) 是戒指上相關(guān)功能的集合,例如心率監(jiān)測服務(wù)或電池狀態(tài)服務(wù)。每個服務(wù)都有一個唯一標(biāo)識符 (UUID) ,客戶端通過它來找到對應(yīng)服務(wù)。
  • 特征 是每個服務(wù)中的具體數(shù)據(jù)點或控制機(jī)制。例如,它們可能是只讀 (獲取傳感器數(shù)據(jù)) 、只寫 (發(fā)送命令) 或兩者兼有。有些特征還能在其值發(fā)生變化時自動通知手機(jī),這對于實時健康監(jiān)測尤為重要。

當(dāng)手機(jī)連接到戒指時,會定位所需的服務(wù),并與特定特征交互以發(fā)送命令或接收數(shù)據(jù)。這種結(jié)構(gòu)化的方法不僅確保了通信效率,還能延長電池使用時間。了解了這些基礎(chǔ)知識后,讓我們開始構(gòu)建吧!

設(shè)置 Xcode 項目

創(chuàng)建一個名為 Halo 的新項目,目標(biāo)平臺為 iOS。組織標(biāo)識符建議使用反向域名格式 (如 com.example) 。本項目中,我們使用 com.FirstNameLastName

接下來,為應(yīng)用啟用必要的功能。在 Xcode 中,打開 Signing & Capabilities 選項卡,啟用以下 后臺模式 (Background Modes),以確保應(yīng)用在后臺運行時能夠保持與戒指的連接并處理數(shù)據(jù)。

然后,我們將使用 Apple 提供的最新框架AccessorySetupKit,用于將藍(lán)牙和 Wi-Fi 配件連接到 iOS 應(yīng)用。此框架自 iOS 18 推出,替代了傳統(tǒng)的廣泛藍(lán)牙權(quán)限請求方式,專注于為用戶明確批準(zhǔn)的特定設(shè)備提供訪問權(quán)限。

  • AccessorySetupKithttps://developer.apple.com/documentation/accessorysetupkit/

當(dāng)用戶嘗試將 COLMI R02 戒指連接到應(yīng)用時,AccessorySetupKit 會顯示一個系統(tǒng)界面,僅列出兼容的附近設(shè)備。用戶選擇設(shè)備后,應(yīng)用即可與戒指通信,而無需請求完整的藍(lán)牙權(quán)限。這大大提升了用戶隱私,同時簡化了設(shè)備連接的管理流程。

打開 Info.plist 文件 (可以在左側(cè)邊欄中找到,或通過 Project Navigator (?1) > Your Target > Info 定位) 。添加以下鍵值條目以支持與 COLMI R02 戒指的配對:

  • 添加 NSAccessorySetupKitSupports,類型為 Array,并將 Bluetooth 作為第一個項目。
  • 添加 NSAccessorySetupBluetoothServices,類型為 Array,并將以下 UUID 作為 String 項:
    • 6E40FFF0-B5A3-F393-E0A9-E50E24DCCA9E
    • 0000180A-0000-1000-8000-00805F9B34FB

至此,初步配置完成!??

圖片

Ring Session Manager 類

接下來,我們將創(chuàng)建一個 RingSessionManager 類,用于管理所有與戒指的通信。此類的主要職責(zé)包括:

  • 掃描附近的戒指
  • 連接到戒指
  • 發(fā)現(xiàn)服務(wù)和特征
  • 實現(xiàn)數(shù)據(jù)讀寫操作

第一步:創(chuàng)建 RingSessionManager

首先創(chuàng)建一個新的 Swift 文件 (?N) ,命名為 RingSessionManager.swift。以下是類的定義以及需要實現(xiàn)的關(guān)鍵屬性:

@Observable
class RingSessionManagerNSObject {
    // 追蹤連接狀態(tài)
    var peripheralConnected = false
    var pickerDismissed = true

    // 存儲當(dāng)前連接的戒指
    var currentRing: ASAccessory?
    private var session = ASAccessorySession()

    // 核心藍(lán)牙對象
    private var manager: CBCentralManager?
    private var peripheral: CBPeripheral?
}

第二步:發(fā)現(xiàn)戒指

戒指通過特定的藍(lán)牙服務(wù) UUID 進(jìn)行廣播。為了找到它,我們需要創(chuàng)建一個 ASDiscoveryDescriptor 對象,指定其藍(lán)牙服務(wù)的 UUID。以下代碼完成了這一功能:

private static let ring: ASPickerDisplayItem = {
    let descriptor = ASDiscoveryDescriptor()
    descriptor.bluetoothServiceUUID = CBUUID(string: '6E40FFF0-B5A3-F393-E0A9-E50E24DCCA9E')
    return ASPickerDisplayItem(
        name: 'COLMI R02 Ring',
        productImage: UIImage(named: 'colmi')!,
        descriptor: descriptor
    )
}()

確保將戒指圖片添加到項目資源目錄中,或者用合適的占位符替換 UIImage(named: 'colmi')!。

第三步:顯示戒指選擇器

為了讓用戶選擇戒指,我們調(diào)用系統(tǒng)內(nèi)置的設(shè)備選擇器界面:

func presentPicker() {
    session.showPicker(for: [Self.ring]) { error in
        if let error {
            print('Failed to show picker: \(error.localizedDescription)')
        }
    }
}

第四步:處理戒指選擇

當(dāng)用戶從選擇器中選定設(shè)備后,應(yīng)用需要處理連接和管理邏輯。以下代碼實現(xiàn)了事件處理:

private func handleSessionEvent(event: ASAccessoryEvent) {
    switch event.eventType {
    case .accessoryAdded:
        guard let ring = event.accessory else { return }
        saveRing(ring: ring)
    case .activated:
        // 重新連接已配對戒指
        guard let ring = session.accessories.first else { return }
        saveRing(ring: ring)
    case .accessoryRemoved:
        currentRing = nil
        manager = nil
    }
}

第五步:建立連接

完成選擇戒指后,我們需要與其建立藍(lán)牙連接:

func connect() {
    guard let manager, manager.state == .poweredOn, let peripheral else { return }
    let options: [StringAny] = [
        CBConnectPeripheralOptionNotifyOnConnectionKeytrue,
        CBConnectPeripheralOptionNotifyOnDisconnectionKeytrue,
        CBConnectPeripheralOptionStartDelayKey1
    ]
    manager.connect(peripheral, options: options)
}

第六步:理解委托方法

RingSessionManager 中,我們實現(xiàn)了兩個關(guān)鍵的委托協(xié)議,用于管理藍(lán)牙通信過程。

中央管理器委托 (CBCentralManagerDelegate)此委托主要處理藍(lán)牙連接的整體狀態(tài)。

func centralManagerDidUpdateState(_ central: CBCentralManager) {
    print('Central manager state: \(central.state)')
    switch central.state {
    case .poweredOn:
        if let peripheralUUID = currentRing?.bluetoothIdentifier {
            if let knownPeripheral = central.retrievePeripherals(withIdentifiers: [peripheralUUID]).first {
                print('Found previously connected peripheral')
                peripheral = knownPeripheral
                peripheral?.delegate = self
                connect()
            } else {
                print('Known peripheral not found, starting scan')
            }
        }
    default:
        peripheral = nil
    }
}

當(dāng)藍(lán)牙開啟時,程序會檢查是否有已連接的戒指,并嘗試重新連接。
成功連接后:

func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
    print('DEBUG: Connected to peripheral: \(peripheral)')
    peripheral.delegate = self
    print('DEBUG: Discovering services...')
    peripheral.discoverServices([CBUUID(string: Self.ringServiceUUID)])
    peripheralConnected = true
}

斷開連接時:

func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: (any Error)?) {
    print('Disconnected from peripheral: \(peripheral)')
    peripheralConnected = false
    characteristicsDiscovered = false
}

外設(shè)委托 (CBPeripheralDelegate)

此委托主要處理與戒指的具體通信。
首先發(fā)現(xiàn)戒指的服務(wù):

func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: (any Error)?) {
    print('DEBUG: Services discovery callback, error: \(String(describing: error))')
    guard error == nillet services = peripheral.services else {
        print('DEBUG: No services found or error occurred')
        return
    }
    print('DEBUG: Found \(services.count) services')
    for service in services {
        if service.uuid == CBUUID(string: Self.ringServiceUUID) {
            print('DEBUG: Found ring service, discovering characteristics...')
            peripheral.discoverCharacteristics([
                CBUUID(string: Self.uartRxCharacteristicUUID),
                CBUUID(string: Self.uartTxCharacteristicUUID)
            ], for: service)
        }
    }
}

發(fā)現(xiàn)特征后:

func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
    print('DEBUG: Characteristics discovery callback, error: \(String(describing: error))')
    guard error == nillet characteristics = service.characteristics else {
        print('DEBUG: No characteristics found or error occurred')
        return
    }
    print('DEBUG: Found \(characteristics.count) characteristics')
    for characteristic in characteristics {
        switch characteristic.uuid {
        case CBUUID(string: Self.uartRxCharacteristicUUID):
            print('DEBUG: Found UART RX characteristic')
            self.uartRxCharacteristic = characteristic
        case CBUUID(string: Self.uartTxCharacteristicUUID):
            print('DEBUG: Found UART TX characteristic')
            self.uartTxCharacteristic = characteristic
            peripheral.setNotifyValue(truefor: characteristic)
        default:
            print('DEBUG: Found other characteristic: \(characteristic.uuid)')
        }
    }
    characteristicsDiscovered = true
}

接收數(shù)據(jù)時:

func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
    if characteristic.uuid == CBUUID(string: Self.uartTxCharacteristicUUID) {
        if let value = characteristic.value {
            print('Received value: \(value)')
        }
    }
}

發(fā)送命令后:

func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
    if let error = error {
        print('Write to characteristic failed: \(error.localizedDescription)')
    } else {
        print('Write to characteristic successful')
    }
}
完整代碼

完整的 RingSessionManager 類代碼如下:

import Foundation
import AccessorySetupKit
import CoreBluetooth
import SwiftUI

@Observable
class RingSessionManagerNSObject {
    var peripheralConnected = false
    var pickerDismissed = true
    
    var currentRing: ASAccessory?
    private var session = ASAccessorySession()
    private var manager: CBCentralManager?
    private var peripheral: CBPeripheral?
    
    private var uartRxCharacteristic: CBCharacteristic?
    private var uartTxCharacteristic: CBCharacteristic?
    
    private static let ringServiceUUID = '6E40FFF0-B5A3-F393-E0A9-E50E24DCCA9E'
    private static let uartRxCharacteristicUUID = '6E400002-B5A3-F393-E0A9-E50E24DCCA9E'
    private static let uartTxCharacteristicUUID = '6E400003-B5A3-F393-E0A9-E50E24DCCA9E'
    
    private static let deviceInfoServiceUUID = '0000180A-0000-1000-8000-00805F9B34FB'
    private static let deviceHardwareUUID = '00002A27-0000-1000-8000-00805F9B34FB'
    private static let deviceFirmwareUUID = '00002A26-0000-1000-8000-00805F9B34FB'
    
    private static let ring: ASPickerDisplayItem = {
        let descriptor = ASDiscoveryDescriptor()
        descriptor.bluetoothServiceUUID = CBUUID(string: ringServiceUUID)
        
        return ASPickerDisplayItem(
            name: 'COLMI R02 Ring',
            productImage: UIImage(named: 'colmi')!,
            descriptor: descriptor
        )
    }()
    
    private var characteristicsDiscovered = false
    
    override init() {
        super.init()
        self.session.activate(on: DispatchQueue.main, eventHandler: handleSessionEvent(event:))
    }
    
    // MARK: - RingSessionManager actions
    func presentPicker() {
        session.showPicker(for: [Self.ring]) { error in
            if let error {
                print('Failed to show picker due to: \(error.localizedDescription)')
            }
        }
    }
    
    func removeRing() {
        guard let currentRing else { return }
        
        if peripheralConnected {
            disconnect()
        }
        
        session.removeAccessory(currentRing) { _ in
            self.currentRing = nil
            self.manager = nil
        }
    }
    
    func connect() {
        guard
            let manager, manager.state == .poweredOn,
            let peripheral
        else {
            return
        }
        let options: [StringAny] = [
            CBConnectPeripheralOptionNotifyOnConnectionKeytrue,
            CBConnectPeripheralOptionNotifyOnDisconnectionKeytrue,
            CBConnectPeripheralOptionStartDelayKey1
        ]
        manager.connect(peripheral, options: options)
    }
    
    func disconnect() {
        guard let peripheral, let manager else { return }
        manager.cancelPeripheralConnection(peripheral)
    }
    
    // MARK: - ASAccessorySession functions
    private func saveRing(ring: ASAccessory) {
        currentRing = ring
        
        if manager == nil {
            manager = CBCentralManager(delegate: self, queue: nil)
        }
    }
    
    private func handleSessionEvent(event: ASAccessoryEvent) {
        switch event.eventType {
        case .accessoryAdded, .accessoryChanged:
            guard let ring = event.accessory else { return }
            saveRing(ring: ring)
        case .activated:
            guard let ring = session.accessories.first else { return }
            saveRing(ring: ring)
        case .accessoryRemoved:
            self.currentRing = nil
            self.manager = nil
        case .pickerDidPresent:
            pickerDismissed = false
        case .pickerDidDismiss:
            pickerDismissed = true
        default:
            print('Received event type \(event.eventType)')
        }
    }
}

// MARK: - CBCentralManagerDelegate
extension RingSessionManagerCBCentralManagerDelegate {
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        print('Central manager state: \(central.state)')
        switch central.state {
        case .poweredOn:
            if let peripheralUUID = currentRing?.bluetoothIdentifier {
                if let knownPeripheral = central.retrievePeripherals(withIdentifiers: [peripheralUUID]).first {
                    print('Found previously connected peripheral')
                    peripheral = knownPeripheral
                    peripheral?.delegate = self
                    connect()
                } else {
                    print('Known peripheral not found, starting scan')
                }
            }
        default:
            peripheral = nil
        }
    }
    
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        print('DEBUG: Connected to peripheral: \(peripheral)')
        peripheral.delegate = self
        print('DEBUG: Discovering services...')
        peripheral.discoverServices([CBUUID(string: Self.ringServiceUUID)])
        
        peripheralConnected = true
    }
    
    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: (any Error)?) {
        print('Disconnected from peripheral: \(peripheral)')
        peripheralConnected = false
        characteristicsDiscovered = false
    }
    
    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: (any Error)?) {
        print('Failed to connect to peripheral: \(peripheral), error: \(error.debugDescription)')
    }
}

// MARK: - CBPeripheralDelegate
extension RingSessionManagerCBPeripheralDelegate {
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: (any Error)?) {
        print('DEBUG: Services discovery callback, error: \(String(describing: error))')
        guard error == nillet services = peripheral.services else {
            print('DEBUG: No services found or error occurred')
            return
        }
        
        print('DEBUG: Found \(services.count) services')
        for service in services {
            if service.uuid == CBUUID(string: Self.ringServiceUUID) {
                print('DEBUG: Found ring service, discovering characteristics...')
                peripheral.discoverCharacteristics([
                    CBUUID(string: Self.uartRxCharacteristicUUID),
                    CBUUID(string: Self.uartTxCharacteristicUUID)
                ], for: service)
            }
        }
    }
    
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        print('DEBUG: Characteristics discovery callback, error: \(String(describing: error))')
        guard error == nillet characteristics = service.characteristics else {
            print('DEBUG: No characteristics found or error occurred')
            return
        }
        
        print('DEBUG: Found \(characteristics.count) characteristics')
        for characteristic in characteristics {
            switch characteristic.uuid {
            case CBUUID(string: Self.uartRxCharacteristicUUID):
                print('DEBUG: Found UART RX characteristic')
                self.uartRxCharacteristic = characteristic
            case CBUUID(string: Self.uartTxCharacteristicUUID):
                print('DEBUG: Found UART TX characteristic')
                self.uartTxCharacteristic = characteristic
                peripheral.setNotifyValue(truefor: characteristic)
            default:
                print('DEBUG: Found other characteristic: \(characteristic.uuid)')
            }
        }
        characteristicsDiscovered = true
    }
    
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        if characteristic.uuid == CBUUID(string: Self.uartTxCharacteristicUUID) {
            if let value = characteristic.value {
                print('Received value: \(value)')
            }
        }
    }
    
    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
        if let error = error {
            print('Write to characteristic failed: \(error.localizedDescription)')
        } else {
            print('Write to characteristic successful')
        }
    }
}

最后一步:將其應(yīng)用到我們的應(yīng)用程序中

ContentView.swift 中粘貼以下代碼,作為主界面的一部分:

import SwiftUI
import AccessorySetupKit

struct ContentViewView {
    @State var ringSessionManager = RingSessionManager()
    var body: some View {
        List {
            Section('MY DEVICE', content: {
                if ringSessionManager.pickerDismissed, let currentRing = ringSessionManager.currentRing {
                    makeRingView(ring: currentRing)
                } else {
                    Button {
                        ringSessionManager.presentPicker()
                    } label: {
                        Text('Add Ring')
                            .frame(maxWidth: .infinity)
                            .font(Font.headline.weight(.semibold))
                    }
                }
            })
        }.listStyle(.insetGrouped)
    }
    
    @ViewBuilder
    private func makeRingView(ring: ASAccessory) -> some View {
        HStack {
            Image('colmi')
                .resizable()
                .aspectRatio(contentMode: .fit)
                .frame(height: 70)
            VStack(alignment: .leading) {
                Text(ring.displayName)
                    .font(Font.headline.weight(.semibold))
            }
        }
    }
}

#Preview {
    ContentView()
}

如果一切配置正確,你現(xiàn)在可以構(gòu)建并運行應(yīng)用。當(dāng)點擊“Add Ring”按鈕時,將彈出一個界面,顯示附近的兼容設(shè)備 (包括 COLMI R02 戒指) 。選擇設(shè)備后,應(yīng)用即可完成連接。??

圖片
連接演示

在后續(xù)的文章中,我們將進(jìn)一步探索如何與戒指交互,包括讀取電池電量、獲取傳感器數(shù)據(jù) (如 PPG 和加速度計) ,并基于這些數(shù)據(jù)開發(fā)實時心率監(jiān)測、活動追蹤及睡眠檢測功能。敬請期待!

英文原文:https:///blog/cyrilzakka/halo-introduction

原文作者: Cyril, ML Researcher, Health AI Lead @ Hugging Face

譯者: Lu Cheng, Hugging Face Fellow

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多