Halo 正式开源: 使用可穿戴设备进行开源健康追踪

在飞速发展的可穿戴技术领域,我们正处于一个十字路口——市场上充斥着各式时尚、功能丰富的设备,声称能够彻底改变我们对健康和健身的方式。
然而,在这些光鲜的外观和营销宣传背后,隐藏着一个令人担忧的现实:大多数这些设备是封闭系统,其内部运行被专有代码和封闭硬件所掩盖。作为消费者,我们对这些设备如何收集、处理及可能共享我们的健康数据一无所知。
这时,Halo 出现了,它是一种旨在让健康追踪更加普惠化的开源替代方案。通过这系列文章,我们将引导你从基础入手,构建并使用完全透明、可定制的可穿戴设备。
需要说明的是,Halo 的目标并不是在外观或功能完整性上与消费级可穿戴设备竞争。相反,它提供了一种独特的、动手实践的方式来理解健康追踪设备背后的技术。
我们将使用 Swift 5 来构建对应的 iOS 界面,Python >= 3.10。由于此项目的代码完全开源,你可以随时提交 PR 拉取请求,或者 Fork 分叉项目以探索全新的方向。
开源https://github.com/cyrilzakka/Halo-iOS
你将需要:
获取COLMI R02实体设备,价格在撰写时为 11 到 30 美金左右。https://www.aliexpress.us/item/3256806445134241.html?gatewayAdapt=glo2usa4itemAdapt
一个安装了 Xcode 16 的开发环境,以及可选的 Apple 开发者计划会员资格。
Python >= 3.10,并安装了pandas、numpy、torch当然还有transformers。
致谢
此项目基于Python 仓库的代码及我的学习成果构建。
Python 仓库https://tahnok.github.io/colmi_r02_client/
免责声明
作为一名医生,我有法律义务提醒你:你即将阅读的内容并不是医学建议。现在,让我们开始让一些可穿戴设备发出蜂鸣声吧!
配对戒指
在进入代码之前,让我们先了解蓝牙低能耗 (BLE) 的关键规格。BLE 基于一个简单的客户端-服务器模型,使用三个核心概念:中央设备 (Centrals) 、服务 (Services) 和 **特征 (Characteristics)**。以下是它们的具体介绍:
中央设备 (例如你的 iPhone) 负责启动和管理与外设 (例如我们的 COLMI R02 戒指) 的连接。戒指通过广播自身信息等待手机连接,每次仅支持一台手机连接。
服务 是戒指上相关功能的集合,例如心率监测服务或电池状态服务。每个服务都有一个唯一标识符 (UUID) ,客户端通过它来找到对应服务。
特征 是每个服务中的具体数据点或控制机制。例如,它们可能是只读 (获取传感器数据) 、只写 (发送命令) 或两者兼有。有些特征还能在其值发生变化时自动通知手机,这对于实时健康监测尤为重要。
当手机连接到戒指时,会定位所需的服务,并与特定特征交互以发送命令或接收数据。这种结构化的方法不仅确保了通信效率,还能延长电池使用时间。了解了这些基础知识后,让我们开始构建吧!
设置 Xcode 项目
创建一个名为 Halo 的新项目,目标平台为 iOS。组织标识符建议使用反向域名格式 (如 com.example) 。本项目中,我们使用 com.FirstNameLastName。
接下来,为应用启用必要的功能。在 Xcode 中,打开 Signing & Capabilities 选项卡,启用以下 后台模式 (Background Modes),以确保应用在后台运行时能够保持与戒指的连接并处理数据。
然后,我们将使用 Apple 提供的最新框架AccessorySetupKit,用于将蓝牙和 Wi-Fi 配件连接到 iOS 应用。此框架自 iOS 18 推出,替代了传统的广泛蓝牙权限请求方式,专注于为用户明确批准的特定设备提供访问权限。
AccessorySetupKithttps://developer.apple.com/documentation/accessorysetupkit/
当用户尝试将 COLMI R02 戒指连接到应用时,AccessorySetupKit 会显示一个系统界面,仅列出兼容的附近设备。用户选择设备后,应用即可与戒指通信,而无需请求完整的蓝牙权限。这大大提升了用户隐私,同时简化了设备连接的管理流程。
打开 Info.plist 文件 (可以在左侧边栏中找到,或通过 Project Navigator (⌘1) > Your Target > Info 定位) 。添加以下键值条目以支持与 COLMI R02 戒指的配对:
添加
NSAccessorySetupKitSupports,类型为Array,并将Bluetooth作为第一个项目。添加
NSAccessorySetupBluetoothServices,类型为Array,并将以下 UUID 作为String项:6E40FFF0-B5A3-F393-E0A9-E50E24DCCA9E0000180A-0000-1000-8000-00805F9B34FB
至此,初步配置完成!🤗
Ring Session Manager 类
接下来,我们将创建一个 RingSessionManager 类,用于管理所有与戒指的通信。此类的主要职责包括:
扫描附近的戒指
连接到戒指
发现服务和特征
实现数据读写操作
第一步:创建 RingSessionManager
首先创建一个新的 Swift 文件 (⌘N) ,命名为 RingSessionManager.swift。以下是类的定义以及需要实现的关键属性:
@Observable
class RingSessionManager: NSObject {// 追踪连接状态var peripheralConnected = falsevar pickerDismissed = true// 存储当前连接的戒指var currentRing: ASAccessory?private var session = ASAccessorySession()// 核心蓝牙对象private var manager: CBCentralManager?private var peripheral: CBPeripheral?
} 第二步:发现戒指
戒指通过特定的蓝牙服务 UUID 进行广播。为了找到它,我们需要创建一个 ASDiscoveryDescriptor 对象,指定其蓝牙服务的 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")!。
第三步:显示戒指选择器
为了让用户选择戒指,我们调用系统内置的设备选择器界面:
func presentPicker() {session.showPicker(for: [Self.ring]) { error inif let error {print("Failed to show picker: \(error.localizedDescription)")}}
} 第四步:处理戒指选择
当用户从选择器中选定设备后,应用需要处理连接和管理逻辑。以下代码实现了事件处理:
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 = nilmanager = nil}
} 第五步:建立连接
完成选择戒指后,我们需要与其建立蓝牙连接:
func connect() {guard let manager, manager.state == .poweredOn, let peripheral else { return }let options: [String: Any] = [CBConnectPeripheralOptionNotifyOnConnectionKey: true,CBConnectPeripheralOptionNotifyOnDisconnectionKey: true,CBConnectPeripheralOptionStartDelayKey: 1]manager.connect(peripheral, options: options)
} 第六步:理解委托方法
在 RingSessionManager 中,我们实现了两个关键的委托协议,用于管理蓝牙通信过程。
中央管理器委托 (CBCentralManagerDelegate)此委托主要处理蓝牙连接的整体状态。
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 = knownPeripheralperipheral?.delegate = selfconnect()} 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 = selfprint("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 = falsecharacteristicsDiscovered = false
} 外设委托 (CBPeripheralDelegate)
此委托主要处理与戒指的具体通信。
首先发现戒指的服务:
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: (any Error)?) {print("DEBUG: Services discovery callback, error: \(String(describing: error))")guard error == nil, let 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 == nil, let 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 = characteristiccase CBUUID(string: Self.uartTxCharacteristicUUID):print("DEBUG: Found UART TX characteristic")self.uartTxCharacteristic = characteristicperipheral.setNotifyValue(true, for: 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")}
} 完整代码
完整的 RingSessionManager 类代码如下:
import Foundation
import AccessorySetupKit
import CoreBluetooth
import SwiftUI@Observable
class RingSessionManager: NSObject {var peripheralConnected = falsevar pickerDismissed = truevar 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 = falseoverride init() {super.init()self.session.activate(on: DispatchQueue.main, eventHandler: handleSessionEvent(event:))}// MARK: - RingSessionManager actionsfunc presentPicker() {session.showPicker(for: [Self.ring]) { error inif let error {print("Failed to show picker due to: \(error.localizedDescription)")}}}func removeRing() {guard let currentRing else { return }if peripheralConnected {disconnect()}session.removeAccessory(currentRing) { _ inself.currentRing = nilself.manager = nil}}func connect() {guardlet manager, manager.state == .poweredOn,let peripheralelse {return}let options: [String: Any] = [CBConnectPeripheralOptionNotifyOnConnectionKey: true,CBConnectPeripheralOptionNotifyOnDisconnectionKey: true,CBConnectPeripheralOptionStartDelayKey: 1]manager.connect(peripheral, options: options)}func disconnect() {guard let peripheral, let manager else { return }manager.cancelPeripheralConnection(peripheral)}// MARK: - ASAccessorySession functionsprivate func saveRing(ring: ASAccessory) {currentRing = ringif 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 = nilself.manager = nilcase .pickerDidPresent:pickerDismissed = falsecase .pickerDidDismiss:pickerDismissed = truedefault:print("Received event type \(event.eventType)")}}
}// MARK: - CBCentralManagerDelegate
extension RingSessionManager: CBCentralManagerDelegate {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 = knownPeripheralperipheral?.delegate = selfconnect()} 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 = selfprint("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 = falsecharacteristicsDiscovered = false}func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: (any Error)?) {print("Failed to connect to peripheral: \(peripheral), error: \(error.debugDescription)")}
}// MARK: - CBPeripheralDelegate
extension RingSessionManager: CBPeripheralDelegate {func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: (any Error)?) {print("DEBUG: Services discovery callback, error: \(String(describing: error))")guard error == nil, let 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 == nil, let 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 = characteristiccase CBUUID(string: Self.uartTxCharacteristicUUID):print("DEBUG: Found UART TX characteristic")self.uartTxCharacteristic = characteristicperipheral.setNotifyValue(true, for: 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")}}
} 最后一步:将其应用到我们的应用程序中
在 ContentView.swift 中粘贴以下代码,作为主界面的一部分:
import SwiftUI
import AccessorySetupKitstruct ContentView: View {@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)}@ViewBuilderprivate 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()
} 如果一切配置正确,你现在可以构建并运行应用。当点击“Add Ring”按钮时,将弹出一个界面,显示附近的兼容设备 (包括 COLMI R02 戒指) 。选择设备后,应用即可完成连接。🎉
在后续的文章中,我们将进一步探索如何与戒指交互,包括读取电池电量、获取传感器数据 (如 PPG 和加速度计) ,并基于这些数据开发实时心率监测、活动追踪及睡眠检测功能。敬请期待!
英文原文:https://hf.co/blog/cyrilzakka/halo-introduction
原文作者: Cyril, ML Researcher, Health AI Lead @ Hugging Face
译者: Lu Cheng, Hugging Face Fellow
相关文章:
Halo 正式开源: 使用可穿戴设备进行开源健康追踪
在飞速发展的可穿戴技术领域,我们正处于一个十字路口——市场上充斥着各式时尚、功能丰富的设备,声称能够彻底改变我们对健康和健身的方式。 然而,在这些光鲜的外观和营销宣传背后,隐藏着一个令人担忧的现实:大多数这些…...
summernote富文本批量上传音频,视频等附件
普通项目,HTML的summernote富文本批量上传音频,视频等附件(其他附件同理) JS和CSS的引入 <head><th:block th:include"include :: summernote-css" /> </head> <body><th:block th:include"include :: summernote-js" /> …...
IDEA如何设置编码格式,字符编码,全局编码和项目编码格式
前言 大家好,我是小徐啊。我们在开发Java项目(Springboot)的时候,一般都是会设置好对应的编码格式的。如果设置的不恰当,容易造成乱码的问题,这是要避免的。今天,小徐就来介绍下我们如何在IDEA…...
【计算机网络实验】之静态路由配置
【计算机网络实验】之静态路由配置 实验题目实验目的实验任务实验设备实验环境实验步骤路由器配置设置静态路由测试路由器之间的连通性配置主机PC的IP测试 实验题目 静态路由协议的配置 实验目的 熟悉路由器工作原理和机制;巩固静态路由理论;设计简单…...
十五届蓝桥杯赛题-c/c++ 大学b组
握手问题 很简单,相互牵手即可,但是要注意,第一个人只能与其他49个人牵手,所以开头是加上49 #include <iostream> using namespace std; int main() {int cnt0;for(int i49;i>7;i--){cnti;//cout<<i<<&quo…...
基础自动化系统的任务
基础自动化系统的任务主要包括实现自动控制、提高生产效率、减少人工干预等。以下是其具体任务的相关介绍: 实现自动控制 控制机器设备:基础自动化系统通过预设的程序和逻辑规则,对机器或设备进行自动控制和运行。执行特定任务:这…...
DBeaver添加地图查看器的自定义底图
DBeaver提供了空间数据在地图上查看的功能,地图查看器技术上基于Leaflet实现。 当我们在表格中选择图形列时,空间数据会叠加在右侧的地图查看器上。 其本质是在缓存中会生成一个静态页面,点击查看器左下角的“在浏览器中打开”,可…...
STM32F103C8T6实时时钟RTC
目录 前言 一、RTC基本硬件结构 二、Unix时间戳 2.1 unix时间戳定义 2.2 时间戳与日历日期时间的转换 2.3 指针函数使用注意事项 三、RTC和BKP硬件结构 四、驱动代码解析 前言 STM32F103C8T6外部低速时钟LSE(一般为32.768KHz)用的引脚是PC14和PC…...
Python Selenium:Web自动化测试与爬虫开发
Python Selenium:Web自动化测试与爬虫开发 Python Selenium:Web自动化测试与爬虫开发安装Selenium设置WebDriver基础示例页面元素交互处理JavaScript和Cookies浏览器控制屏幕截图Headless Mode结束会话错误处理与调试 ***本文由AI辅助生成*** Python Se…...
Java-07 深入浅出 MyBatis - 一对多模型 SqlMapConfig 与 Mapper 详细讲解测试
点一下关注吧!!!非常感谢!!持续更新!!! 大数据篇正在更新!https://blog.csdn.net/w776341482/category_12713819.html 目前已经更新到了: MyBatisÿ…...
用CAXA CAD电子图板导入图框、标题栏并导出pdf的方法
1.导入图框: 点击调入图框->出现读入图框文件 一个一个点击,选择合适的图框 然后点击导入 2.导入标题栏: 调入标题栏->出现读入标题栏文件 一个一个点击,选择合适的标题栏,然后点击导入 3.导出pdf&#x…...
深入了解 Linux htop 命令:功能、用法与示例
文章目录 深入了解 Linux htop 命令:功能、用法与示例什么是 htop?htop 的安装htop的基本功能A区:系统资源使用情况B区:系统概览信息C区:进程列表D区:功能键快捷方式 与 top 的对比常见用法与示例实际场景应…...
JDK1.8新增特性
新特性: Lambda表达式: (语法三要素:参数、箭头、代码) JDK1.8引入的一种新语法Lambda表达式,它简化了匿名内部类的使用和提高代码的可读性。 /**正常写法创建Runable**/ Runnable runnable new Runnable() {Overridepublic voi…...
环境背景文本到语音转换
目录 概述演示效果核心逻辑使用方式 概述 本文所涉及的所有资源的获取方式:https://www.aspiringcode.com/content?id100000000027&uid2f1061526e3a4548ab2e111ad079ea8c 论文标题: 本文提出了 VoiceLDM,这是一种旨在生成准确遵循两种…...
后端数据增删改查基于Springboot+mybatis mysql 时间根据当时时间自动填充,数据库连接查询不一致,mysql数据库连接不好用
目录 后端数据增删改查Springboot 实体(entity)类引进添加UserMapper接口 创建对用的UserController注意数据库查询不一致新增数据更新删除postman测试 后端数据增删改查 基于之前构建系统,实现用户数据的CRUD。 打开navicat16,…...
《Python编程实训快速上手》第九天--调试技巧
一、抛异常 异常类型分为两类,第一类是Python自带的异常类型(见《Python编程快速上手》第一天---前三章打基础),第二类是自定义异常。 面对自定义异常类型,使用raise抛异常,类型值默认为Exception&#x…...
html5复习一
目标 1、html5介绍及开发工具 2、html5标签 3、文本样式 4、图片标签和超链接标签 知识点: 万维网的构成: 1、url:统一资源定位器 2、http/https:超文本传输协议 3、html:超文本标记语言 html的后缀名: .html 和 .htm html基本…...
SSL/TLS,SSL,TLS分别是什么
SSL/TLS,SSL,TLS分别是什么 SSL(Secure Sockets Layer,安全套接层) 定义与发展历程: SSL 是一种早期的网络安全协议,旨在为网络通信提供保密性、数据完整性和身份验证等安全保障。它最初由网景…...
css iframe标签使用
<iframe> 标签用于在网页中嵌入另一个 HTML 页面。它非常灵活,可用于嵌入内容,比如其他网站、视频、地图等。以下是有关 <iframe> 的详细介绍及使用方法: 基本语法 <iframe src"URL" width"宽度" height…...
API的妙用
我们都知道,通过使用API可以快速开发部署应用,不需要从头开始收集处理数据。能够很好地提高效率。 一、加速应用程序开发和部署 通过调用API接口,可以快速获取数据、实现功能或整合其他服务,无需从零开始编写大量的代码…...
Python爬虫实战:研究MechanicalSoup库相关技术
一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...
基于大模型的 UI 自动化系统
基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...
C++_核心编程_多态案例二-制作饮品
#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...
TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
什么是EULA和DPA
文章目录 EULA(End User License Agreement)DPA(Data Protection Agreement)一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA(End User License Agreement) 定义: EULA即…...
Java面试专项一-准备篇
一、企业简历筛选规则 一般企业的简历筛选流程:首先由HR先筛选一部分简历后,在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如:Boss直聘(招聘方平台) 直接按照条件进行筛选 例如:…...
九天毕昇深度学习平台 | 如何安装库?
pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子: 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...
中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...
在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案
这个问题我看其他博主也写了,要么要会员、要么写的乱七八糟。这里我整理一下,把问题说清楚并且给出代码,拿去用就行,照着葫芦画瓢。 问题 在继承QWebEngineView后,重写mousePressEvent或event函数无法捕获鼠标按下事…...
VM虚拟机网络配置(ubuntu24桥接模式):配置静态IP
编辑-虚拟网络编辑器-更改设置 选择桥接模式,然后找到相应的网卡(可以查看自己本机的网络连接) windows连接的网络点击查看属性 编辑虚拟机设置更改网络配置,选择刚才配置的桥接模式 静态ip设置: 我用的ubuntu24桌…...
