iOS传感器数据采集与云端传输实战:CoreMotion与Adafruit IO集成指南

发布时间:2026/5/19 2:25:38

iOS传感器数据采集与云端传输实战:CoreMotion与Adafruit IO集成指南 1. 项目概述将iPhone传感器数据送上云端如果你手头有一台iPhone想把它变成一个实时数据采集终端比如监测设备的震动、记录运动轨迹或者只是单纯想看看自己走路时手机在口袋里晃得多厉害那么把传感器数据实时发送到云端是个挺酷的主意。我自己在做一些物联网原型验证和运动分析小工具时经常需要这个能力。Adafruit IO作为一个轻量级的物联网数据平台提供了数据接收、存储和可视化的全套服务特别适合个人开发者和小型项目。而Swift和iOS原生的CoreMotion框架则让我们获取传感器数据变得异常简单。这个项目的核心就是打通“设备端数据采集”到“云端数据呈现”这条链路。听起来好像涉及很多环节但拆解开来其实就是三步用CoreMotion拿到iPhone加速度计的实时数据用Swift的网络库将这些数据打包成一个HTTP POST请求最后把这个请求发送到Adafruit IO为你准备好的数据接口Feed上。完成后你就能在Adafruit IO的仪表盘上看到一个实时更新的图表直观地展示手机在X、Y、Z轴上的加速度变化。整个过程不需要复杂的服务器知识你只需要一个Adafruit IO的免费账户、一台安装了Xcode的Mac以及一部用于测试的iPhone或iPad。即使你之前没有太多Swift开发经验跟着步骤走也能搞定。下面我就把从零开始构建这个数据流应用的完整过程、背后的原理以及我趟过的一些坑详细地分享给你。2. 环境准备与项目初始化在写第一行代码之前我们需要把“战场”布置好。这包括开发工具、测试设备以及云端服务的配置。这些准备工作看似琐碎但每一步都关系到后续开发是否顺畅。2.1 开发工具与账户配置首先确保你的Mac上安装了Xcode。这是开发iOS应用的唯一官方IDE。建议使用较新的版本如Xcode 15或更高以避免一些旧的API废弃问题。你可以在Mac App Store中免费下载和更新它。接下来是云端平台。访问Adafruit IO的官网并注册一个免费账户。免费账户有一定的数据点数和请求频率限制但对于我们这种个人学习和原型开发来说完全够用。登录后你需要获取两个关键的密钥AIO Key (Active Key)这是你的账户主密钥相当于访问整个Adafruit IO服务的密码。在网站左侧菜单找到“View AIO Key”点击后弹出的窗口中“Active Key”那一长串字符就是它。请妥善保管不要泄露。Feed Key在Adafruit IO中一个“Feed”就是一个独立的数据流通道你可以把它理解为一个专属的数据信箱。我们需要先创建一个Feed来接收加速度计数据。点击左侧“Feeds”标签然后点击“Create a New Feed”。给它起个名字比如“iphone_accel_x”。创建成功后在Feeds列表中找到它其对应的“Key”就是Feed Key。这个Key是用来向这个特定信箱发送数据的地址标识。注意在实际代码中我们会将这两个Key填入HTTP请求的URL里。一个常见的错误是混淆了它们的位置或者直接使用了教程中的示例URL而忘记替换。务必用自己的Key替换掉代码中的占位符。2.2 创建Xcode项目与基础配置打开Xcode选择“Create a new Xcode project”。在模板选择界面我们选择最基础的“App”在较老版本的Xcode中可能是“Single View App”。点击下一步后进入项目配置页面Product Name给你的应用起个名字例如“SensorStreamer”。Team选择你的Apple开发者账户。如果你只是想在真机上测试使用免费的Apple ID即可。Bundle Identifier通常会自动生成格式如com.yourcompany.SensorStreamer。保持默认即可。Interface选择“Storyboard”。SwiftUI虽然更现代但为了与许多现有教程和代码兼容我们暂时使用传统的Storyboard。Language务必选择“Swift”。不要勾选“Use Core Data”、“Include Tests”等选项以保持项目简洁。点击“Next”选择项目保存的位置然后点击“Create”。项目创建成功后我们首先需要处理一个重要的配置网络请求权限。因为我们的应用需要访问互联网向Adafruit IO发送数据所以必须在配置文件中声明这一点。在Xcode左侧的项目导航器中找到并点击名为Info.plist的文件。我们需要手动添加一个键值对右键点击Info.plist的空白处选择“Add Row”。在出现的键名列表中输入“App Transport Security Settings”。Xcode会自动补全这是一个字典类型的键。点击这个新行左侧的展开箭头在其下添加一个子项键名为“Allow Arbitrary Loads”类型为Boolean并将其值设置为“YES”。这个操作是为了在开发阶段允许应用进行不安全的HTTP网络请求Adafruit IO的API使用HTTPS本不需要此设置但某些网络调试环境下可能需要。为防万一我们先加上。在最终发布应用时应遵循ATSApp Transport Security最佳实践确保所有连接都使用HTTPS。3. 理解CoreMotion框架与数据采集CoreMotion是苹果提供的一个功能强大的框架专门用于处理设备的所有运动相关数据。它就像一个统一的管家管理着加速度计、陀螺仪、磁力计、气压计甚至设备运动融合数据Device Motion的访问。理解它的工作模式是写好传感器应用的关键。3.1 CoreMotion的核心CMMotionManager整个数据采集流程都围绕着一个核心类展开CMMotionManager。你可以把它想象成一个传感器数据的总调度中心。我们不需要直接和硬件传感器打交道而是通过创建这个管理器的一个实例然后向它“订阅”我们感兴趣的数据。在ViewController.swift文件的开头在import UIKit下面我们引入CoreMotion框架并声明这个管理器的实例import UIKit import CoreMotion class ViewController: UIViewController { // 创建运动管理器实例 let motionManager CMMotionManager() // ... 其他代码 }这里使用let声明为一个常量是因为在整个视图控制器的生命周期内我们通常只需要一个共享的CMMotionManager实例。它的主要职责包括检查传感器可用性通过isAccelerometerAvailable等属性判断设备是否具备相应硬件。设置更新频率通过accelerometerUpdateInterval等属性设置我们接收数据的频率单位是秒。启动/停止数据流通过startAccelerometerUpdates等方法开始向我们传递数据通过stopAccelerometerUpdates等方法停止。3.2 加速度计数据解析与采样率选择加速度计测量的是设备在三个轴X, Y, Z上受到的比力单位是重力加速度g约等于9.81 m/s²。当设备静止且屏幕朝上水平放置时Z轴大约为-1g重力方向X和Y轴约为0g。在代码中我们通过设置motionManager.accelerometerUpdateInterval 2.5来指定每2.5秒获取一次数据。这个值的选择很有讲究更小的间隔如0.1秒数据更密集能捕捉快速变化但会消耗更多电量产生更多网络请求。更大的间隔如2.5秒或更长节省电量和流量适合变化缓慢或只需要周期性记录的场景。对于演示或一般监控2.5秒是个合理的起点。但如果你在做手势识别或高频振动分析可能需要将间隔设置为0.01秒100Hz甚至更短。需要注意的是实际能达到的最高频率受硬件限制通常在100Hz左右。你可以通过motionManager.accelerometerUpdateInterval属性设置你期望的频率但系统会尽力满足不一定精确。实操心得在viewDidLoad中直接启动更新并不可取因为用户可能还没准备好或者视图尚未完全呈现。更好的做法是将启动更新的逻辑放在一个按钮或开关的事件响应中让用户来控制数据流的开始和结束。这样也更符合应用的设计规范。数据通过一个“处理器”Handler回调给我们。这个处理器是一个闭包每当有新的传感器数据准备好时就会被调用。我们在这个闭包里拿到一个CMAccelerometerData?类型的可选数据从中可以提取acceleration属性它是一个CMAcceleration结构体包含x,y,z三个Double类型的值。4. 构建用户界面与数据展示一个直观的UI能让数据采集过程变得清晰可见。我们构建一个简单的界面包含用于显示数据的标签和一个用于控制数据流开关的按钮。4.1 使用Storyboard布局UI元素在Xcode项目导航器中找到并点击Main.storyboard文件。这是可视化设计界面的地方。我们需要从右下角的“对象库”Object Library中拖拽几个组件到画布上标签UILabel拖拽两个Label到视图控制器ViewController的画布中央。将上方标签的文本改为“加速度计 X轴数据”这个作为静态标题。下方的标签将用于动态显示数值将其文本初始值设为“--.--”并适当调大字体比如选择System 36.0方便观察。开关UISwitch拖拽一个Switch到画布上放在标签的下方。这个开关将用于启动和停止数据采集。布局完成后大致效果应该是顶部是静态标题标签中间是动态显示的大号数值标签底部是一个开关。你可以使用Storyboard的“对齐”和“约束”工具来让它们在不同尺寸的屏幕上都能居中显示。4.2 建立UI与代码的关联IBOutlet与IBActionUI元素摆好了但它们是“死”的。我们需要在代码中创建对它们的引用才能控制其显示内容和响应其操作。这需要通过建立“连接”来实现。首先点击Xcode右上角的“调整编辑器”按钮选择“Assistant Editor”让Main.storyboard和ViewController.swift并排显示。为数值标签创建Outlet在Main.storyboard中按住键盘的Control键用鼠标从动态数值标签拖拽一条线到ViewController.swift文件中class ViewController: UIViewController {这行代码的下方。在弹出的窗口中“Connection”选择“Outlet”“Name”输入accelTagX然后点击“Connect”。这会在ViewController类中自动生成一行代码IBOutlet weak var accelTagX: UILabel!。现在我们在代码中通过self.accelTagX.text就可以修改这个标签显示的文字了。为开关创建Action同样按住Control键从开关拖拽到ViewController.swift中位置放在刚才创建的Outlet下面。这次在弹出窗口中“Connection”要选择“Action”。“Name”输入stateChanged“Event”默认是“Value Changed”这正好是我们需要的开关状态改变时触发。“Type”选择UISwitch。点击“Connect”。这会生成一个方法IBAction func stateChanged(_ sender: UISwitch) { }。当用户拨动开关时这个方法就会被调用并且传入的sender参数就是这个开关本身我们可以通过sender.isOn来判断开关是开还是关。至此UI和代码的桥梁就搭建好了。界面负责展示和接收操作代码负责逻辑和数据处理。这种MVC模型-视图-控制器的分离模式是iOS开发的基础。5. 实现数据采集与本地显示功能有了UI和CoreMotion的基础我们现在来实现核心的数据采集循环并将实时数据更新到屏幕上。5.1 编写数据采集启动函数我们在ViewController类中创建一个函数startAccelerometerUpdates()。这个函数负责配置CMMotionManager并启动数据流。func startAccelerometerUpdates() { // 1. 检查加速度计是否可用 guard motionManager.isAccelerometerAvailable else { print(该设备不支持加速度计) accelTagX.text 设备不支持 return } // 2. 设置更新间隔单位秒 motionManager.accelerometerUpdateInterval 0.1 // 这里改为0.1秒让数据变化更明显 // 3. 启动更新并指定处理数据的队列和闭包 motionManager.startAccelerometerUpdates(to: .main) { [weak self] (accelerometerData, error) in // 使用[weak self]避免循环引用 guard let self self else { return } // 4. 错误处理 if let error error { print(获取加速度计数据时出错: \(error.localizedDescription)) self.accelTagX.text 获取错误 self.motionManager.stopAccelerometerUpdates() return } // 5. 安全解包并获取数据 if let data accelerometerData { let acceleration data.acceleration let xValue acceleration.x let yValue acceleration.y let zValue acceleration.z // 6. 更新UI必须在主线程 DispatchQueue.main.async { // 格式化显示保留两位小数 self.accelTagX.text String(format: X: %.2f g, xValue) // 如果需要也可以在这里更新Y、Z轴的标签 } // 7. 打印到控制台以便调试 print(String(format: 加速度 - X: %.2f, Y: %.2f, Z: %.2f, xValue, yValue, zValue)) } } }代码解析与注意事项可用性检查这是至关重要的一步。不是所有设备特别是模拟器都有所有传感器。在启动前进行检查可以避免运行时崩溃。更新间隔这里我将间隔设置为0.1秒10Hz这样在屏幕上能看到数值的连续变化体验更好。你可以根据需求调整。启动更新startAccelerometerUpdates(to:withHandler:)方法接收一个队列和一个闭包。我们指定.main主队列因为闭包内需要更新UI。使用[weak self]捕获列表是Swift中防止“循环引用”内存泄漏的标准做法。错误处理网络请求或传感器可能出错在闭包中首先检查error是良好的编程习惯。数据解包accelerometerData是可选类型需要安全解包。UI更新尽管我们指定了主队列但某些情况下双重保障是好的。使用DispatchQueue.main.async确保UI操作一定在主线程执行这是UIKit的要求。调试输出在开发阶段将数据打印到Xcode控制台非常有助于验证数据是否正确采集。5.2 编写数据采集停止函数有开始就要有停止尤其是在用户切换界面或应用进入后台时必须停止传感器更新以节省电量。func stopAccelerometerUpdates() { if motionManager.isAccelerometerActive { motionManager.stopAccelerometerUpdates() accelTagX.text --.-- print(加速度计更新已停止) } }这个函数很简单首先检查加速度计是否正在活跃更新isAccelerometerActive如果是则调用stopAccelerometerUpdates()方法并重置UI显示。5.3 连接开关控制逻辑现在我们需要将开关的动作stateChanged与这两个函数关联起来。IBAction func stateChanged(_ sender: UISwitch) { if sender.isOn { print(开关打开开始采集数据) startAccelerometerUpdates() } else { print(开关关闭停止采集数据) stopAccelerometerUpdates() } }同时我们需要在视图控制器生命周期结束时比如用户退出这个界面确保传感器被停止。在viewWillDisappear方法中添加清理代码是个好习惯override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) stopAccelerometerUpdates() }现在你可以运行应用CommandR到真机上。首次运行可能会提示需要访问运动与健身数据的权限点击允许。拨动开关应该就能看到屏幕上的数值随着你移动手机而实时变化了。到这一步你已经成功实现了传感器数据的本地采集与显示。6. 通过REST API将数据发送至Adafruit IO本地数据显示正常后下一步就是将这些数据通过互联网发送到Adafruit IO平台。我们将使用HTTP协议中的POST请求这是RESTful API中用于创建新资源在我们这里就是新的数据点的标准方法。6.1 理解HTTP POST请求的构成一个典型的向Adafruit IO发送数据的POST请求需要包含以下几个关键部分URL端点数据要发送到的具体地址。Adafruit IO为每个Feed提供了一个唯一的URL。格式https://io.adafruit.com/api/v2/{你的用户名}/feeds/{feed_key}/data示例如果你的用户名是my_adafruit_userFeed Key是iphoneaccelx那么URL就是https://io.adafruit.com/api/v2/my_adafruit_user/feeds/iphoneaccelx/dataHTTP方法必须是POST。请求头Headers需要包含认证信息和内容类型。X-AIO-Key: {你的AIO Key}这是最重要的认证头Adafruit IO用它来识别你的账户。Content-Type: application/json告诉服务器我们发送的数据是JSON格式。请求体Body实际要发送的数据必须是JSON格式。对于Adafruit IO通常是一个包含value键的简单对象。示例{value: 0.75}或{value: -0.12}6.2 使用URLSession构建网络请求Swift标准库中的URLSession是处理网络请求的主力。我们来编写postDataToAdafruitIO函数。首先在ViewController类顶部定义你的密钥和用户名注意这只是为了演示在实际项目中绝对不要将密钥硬编码在代码里应使用安全的方式存储如Keychain Services。// 警告仅为演示。正式项目请使用Keychain或其他安全方式存储密钥。 let adafruitIOUsername 你的Adafruit IO用户名 let adafruitIOKey 你的AIO_Key let feedKey 你的Feed_Key // 例如 iphoneaccelx然后编写发送函数func postDataToAdafruitIO(value: Double) { // 1. 构造URL let urlString https://io.adafruit.com/api/v2/\(adafruitIOUsername)/feeds/\(feedKey)/data guard let url URL(string: urlString) else { print(无效的URL) return } // 2. 创建URLRequest并配置 var request URLRequest(url: url) request.httpMethod POST request.setValue(application/json, forHTTPHeaderField: Content-Type) request.setValue(adafruitIOKey, forHTTPHeaderField: X-AIO-Key) // 3. 准备JSON请求体 let jsonBody: [String: Any] [value: value] // 另一种更Swifty的方式是使用Encodable协议这里为了清晰使用字典。 do { let jsonData try JSONSerialization.data(withJSONObject: jsonBody, options: []) request.httpBody jsonData } catch { print(创建JSON数据失败: \(error)) return } // 4. 创建并启动数据任务 let task URLSession.shared.dataTask(with: request) { [weak self] data, response, error in // 网络请求完成后的回调默认在后台线程 if let error error { print(网络请求错误: \(error.localizedDescription)) return } // 检查HTTP状态码 if let httpResponse response as? HTTPURLResponse { print(状态码: \(httpResponse.statusCode)) // 2xx 状态码表示成功如 200, 201 if !(200...299).contains(httpResponse.statusCode) { print(请求失败状态码: \(httpResponse.statusCode)) // 可以尝试解析返回的数据查看错误信息 if let data data, let responseString String(data: data, encoding: .utf8) { print(失败响应: \(responseString)) } } else { print(数据发送成功) } } // 可选解析成功的响应数据 if let data data { do { if let jsonResponse try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] { print(服务器响应: \(jsonResponse)) } } catch { print(响应JSON解析错误: \(error)) } } } // 5. 启动任务不要忘记这行 task.resume() }关键点解析URL构造使用字符串插值将用户名和Feed Key动态填入URL模板中比手动拼接更安全清晰。请求头设置X-AIO-Key是Adafruit IO特定的认证头必须正确设置。JSON序列化JSONSerialization.data(withJSONObject:)方法将Swift字典转换为JSON格式的二进制数据Data。这里使用了try进行错误处理。URLSession数据任务URLSession.shared.dataTask(with:completionHandler:)创建了一个异步网络任务。completionHandler闭包会在请求完成后被调用无论成功或失败。重要这个闭包默认在后台线程执行如果需要在其中更新UI必须切回主线程DispatchQueue.main.async。任务启动创建task后必须调用task.resume()来启动它否则请求永远不会发出。这是一个常见的疏忽点。状态码检查检查HTTP状态码是调试网络请求的关键。200-299表示成功400-499表示客户端错误如密钥错误、URL错误500-599表示服务器错误。6.3 整合数据采集与网络发送最后一步我们需要在每次采集到新的加速度计数据时调用这个发送函数。修改startAccelerometerUpdates函数中处理数据的闭包部分// ... 在 startAccelerometerUpdates 的数据处理闭包中 ... if let data accelerometerData { let xValue data.acceleration.x // ... 更新UI ... // 调用函数将数据发送到Adafruit IO self.postDataToAdafruitIO(value: xValue) }现在整个流程就串联起来了用户打开开关 - 启动加速度计更新 - 每0.1秒获取一次X轴数据 - 更新本地UI显示 - 同时将该数据通过POST请求发送到Adafruit IO。运行应用打开开关并晃动手机。你可以在Xcode控制台看到打印的发送成功或失败信息。同时登录Adafruit IO网站进入你对应的Feed页面应该能看到一个数据点列表或图表正在实时增加新的数据。7. 项目优化、调试与扩展思路基础功能跑通后我们可以从稳定性、用户体验和功能扩展几个角度来优化这个项目。7.1 网络请求的健壮性优化目前的网络请求在每次传感器更新时都会发起这在快速更新时可能导致请求堆积或失败。我们需要增加一些控制逻辑。请求去重与节流传感器数据变化很快但云端可能不需要如此高频的数据。我们可以设置一个最小发送间隔。class ViewController: UIViewController { let motionManager CMMotionManager() var lastPostTime: Date? // 记录上次发送时间 let minPostInterval: TimeInterval 1.0 // 最小发送间隔1秒 func postDataToAdafruitIO(value: Double) { let now Date() if let lastTime lastPostTime, now.timeIntervalSince(lastTime) minPostInterval { // 距离上次发送时间太短跳过本次发送 return } lastPostTime now // ... 原有的网络请求代码 ... } }错误处理与重试机制网络可能不稳定。简单的重试逻辑可以提升数据送达率。func postDataToAdafruitIO(value: Double, retryCount: Int 0) { // ... 构造请求 ... let task URLSession.shared.dataTask(with: request) { [weak self] data, response, error in if let httpResponse response as? HTTPURLResponse { if httpResponse.statusCode 429 { // 请求过于频繁 print(达到速率限制稍后重试) DispatchQueue.global().asyncAfter(deadline: .now() 5.0) { // 5秒后重试 self?.postDataToAdafruitIO(value: value, retryCount: retryCount 1) } return } // 其他错误处理... } // 网络错误且重试次数小于3次 if let error error, retryCount 3 { print(请求失败进行第\(retryCount1)次重试。错误: \(error)) DispatchQueue.global().asyncAfter(deadline: .now() 2.0) { // 2秒后重试 self?.postDataToAdafruitIO(value: value, retryCount: retryCount 1) } return } // ... 成功或最终失败的处理 ... } task.resume() }后台任务支持如果希望应用退到后台也能持续发送一段时间数据需要开启后台模式能力。在Info.plist中添加“Required background modes”并包含“App registers for location updates”或“App processes data from external accessories”根据实际情况选择且需要合理理由并通过App Store审核。更常见的是使用“Background Tasks”框架来安排短时间的后台工作。7.2 常见问题排查速查表在开发过程中你可能会遇到以下问题。这里提供一个快速排查指南问题现象可能原因排查步骤应用崩溃报错Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value强制解包了一个nil值。最常见的是IBOutlet连接未正确建立导致accelTagX为nil。1. 检查Main.storyboard中标签与ViewController.swift中IBOutlet的连接是否完好右侧圆圈是否实心。2. 检查accelTagX在viewDidLoad之前是否被访问。开关拨动后标签数值无变化1. 传感器更新未启动。2. UI更新未在主线程。3.stateChanged函数未正确关联。1. 在startAccelerometerUpdates函数开始处添加print(“函数被调用”)检查开关触发时是否打印。2. 在数据处理的闭包中添加print(“收到数据:”, xValue)检查是否收到数据。3. 检查Storyboard中开关的Value Changed事件是否连接到stateChanged函数。Xcode控制台打印数据但Adafruit IO收不到1. 网络请求未成功发送。2. AIO Key或Feed Key错误。3. URL格式错误。1. 在postDataToAdafruitIO函数中仔细打印构造的urlString和adafruitIOKey核对是否正确。2. 检查控制台打印的HTTP状态码。401表示认证失败Key错404表示Feed不存在URL错。3. 使用Postman或curl工具直接测试你的URL和Key排除代码问题。数据发送几次后停止1. Adafruit IO免费账户的速率限制默认30点/分钟。2. 设备进入休眠状态网络中断。1. 降低数据发送频率如设置minPostInterval为2秒。2. 在控制台检查是否有429Too Many Requests状态码。3. 在Info.plist中设置UIApplication.shared.isIdleTimerDisabled true临时防止锁屏仅用于测试耗电。模拟器上运行无数据iOS模拟器没有真实的加速度计硬件。motionManager.isAccelerometerAvailable会返回false。必须在真机上进行测试。7.3 功能扩展与项目深化这个基础项目可以沿多个方向扩展成为一个更实用的工具多传感器支持CoreMotion还提供陀螺仪startGyroUpdates、磁力计startMagnetometerUpdates和设备运动数据startDeviceMotionUpdates它融合了多个传感器数据并提供更稳定的姿态估计。你可以为应用添加选项卡或分段控件让用户选择要流式传输的传感器类型。数据持久化与离线缓存在网络不佳时可以将数据临时保存到本地使用CoreData、Realm或简单的文件存储。当网络恢复后再将缓存的数据批量发送到云端。这能确保数据不丢失。自定义Adafruit IO仪表盘Adafruit IO的强大之处在于可视化。你可以在其网站上创建包含多个图块Blocks的仪表盘Dashboard例如折线图实时显示X、Y、Z三轴加速度随时间的变化。仪表盘显示当前加速度的瞬时值。地图如果结合GPS数据需要额外权限和CoreLocation框架可以显示运动轨迹。开关甚至可以反向从Adafruit IO的仪表盘发送一个开关信号到手机远程控制手机的某个功能如开始/停止记录。数据格式丰富化目前只发送了一个数值。你可以发送一个包含时间戳、设备标识符、多个传感器读数的JSON对象。例如{ device_id: iPhone-12-Pro, timestamp: 1681234567.890, accel: {x: 0.12, y: -0.05, z: -0.98}, gyro: {x: 0.01, y: 0.0, z: 0.0} }这需要在Adafruit IO端创建一个能够接收复杂JSON的Feed或者使用更灵活的数据处理方式。加入更多iOS特性使用CoreLocation添加地理位置信息使用BackgroundTasks框架优化后台数据上传添加本地通知在数据达到某个阈值时提醒用户。这个项目就像一个乐高底座掌握了传感器数据采集和网络传输这两个核心模块后你可以根据自己的创意拼接上不同的功能模块构建出各种有趣的物联网应用原型。从简单的数据记录到复杂的远程监控系统其核心原理都是相通的。

相关新闻