智能垃圾桶要实现满溢报警,核心在于准确测量桶内垃圾到桶口的距离。超声波传感器因为成本低、精度适中,成为这类硬件最常见的选型。但对iOS开发者来说,如何通过Core Bluetooth框架稳定地读取传感器数据,并在软件层面做温度补偿和滤波处理,往往比想象中复杂。本文将围绕一个真实的智能垃圾桶项目,完整讲解从扫描连接到数据处理的全部关键步骤。

一、Core Bluetooth连接超声波传感器模块的完整流程
Core Bluetooth是苹果官方提供的低功耗蓝牙(BLE)开发框架,核心角色分为中心设备(Central)和外设(Peripheral)。iPhone作为中心设备,智能垃圾桶上的蓝牙模块(如HC-08、JDY-08或nRF52832)作为外设,周期性地广播包含服务数据的广播包。整个通信链路遵循GATT协议:外设暴露Service,Service下包含Characteristic,中心设备订阅Characteristic的Notify,即可持续接收传感器数据。
首先要初始化CBCentralManager,并在其状态变为poweredOn后开始扫描。注意扫描必须放在状态回调中进行,如果在蓝牙尚未就绪时直接调用scanForPeripherals,会静默失败。示例代码如下:
import CoreBluetooth
class BinCentralManager: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate {
var centralManager: CBCentralManager!
var targetPeripheral: CBPeripheral?
override init() {
super.init()
centralManager = CBCentralManager(delegate: self, queue: nil)
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOn {
// 只扫描包含垃圾桶服务UUID的设备,减少无关设备干扰
centralManager.scanForPeripherals(
withServices: [CBUUID(string: "FFE0")],
options: [CBCentralManagerScanOptionAllowDuplicatesKey: false]
)
}
}
func centralManager(_ central: CBCentralManager,
didDiscover peripheral: CBPeripheral,
advertisementData: [String: Any],
rssi RSSI: NSNumber) {
targetPeripheral = peripheral
targetPeripheral?.delegate = self
centralManager.stopScan()
centralManager.connect(peripheral, options: nil)
}
func centralManager(_ central: CBCentralManager,
didConnect peripheral: CBPeripheral) {
peripheral.discoverServices([CBUUID(string: "FFE0")])
}
}连接成功后,需要在didDiscoverServices回调中继续发现特征值,找到Notify类型的特征并调用setNotifyValue方法订阅。这一步是新手最容易踩坑的地方:特征有read、write、notify三种属性,超声波数据通常通过notify推送,如果只调用了readValue,只会收到一次数据,后续传感器更新将无法到达。
二、超声波测距原理与温度补偿的实现
超声波传感器(如HC-SR04的BLE改造版或集成模块)测距的原理是测量声波从发射到接收回波的时间差,再乘以声速。常温下声速通常取340m/s,但声速实际上与环境温度直接相关,计算公式为:v = 331.4 + 0.6T(单位:米/秒,T为摄氏温度)。垃圾桶通常放置在户外,昼夜温差可能导致温度从零下到四十度以上变化,此时声速会从约318m/s波动到355m/s,误差接近10%。对于满溢判断精度要求5%以内的场景,不做温度补偿几乎不可用。
因此智能垃圾桶硬件上通常额外集成DS18B20数字温度传感器,通过另一个特征值上报温度。iOS端拿到温度后,动态计算当前声速,再换算距离。假设外设通过notify推送的字节流前两字节是回波时间(微秒,16位大端),随后一个字节是温度(实际温度减去40,以支持负温表示),解析与换算代码如下:
func parseDistance(from data: Data) -> Double {
guard data.count >= 3 else { return -1 }
let bytes = [UInt8](data)
// 回波时间,单位微秒,大端序
let echoTime = Double((Int(bytes[0]) << 8) | Int(bytes[1]))
// 温度字节:实际温度 = 原始值 - 40,支持 -40 到 87 度
let temperature = Double(bytes[2]) - 40.0
// 温度补偿后的声速计算:v = 331.4 + 0.6 * T
let soundSpeed = 331.4 + 0.6 * temperature
// 声波往返,距离 = 速度 * 时间 / 2,时间单位换算为秒
let distance = soundSpeed * (echoTime / 1_000_000.0) / 2.0
return distance
}经过温度补偿后,同样的回波时间在不同季节换算出的距离会自动修正。以桶高80厘米的垃圾桶为例,夏季35度和冬季零下5度下,同一真实距离的原始测量偏差可以从4厘米以上降低到1厘米以内,这对满溢阈值的准确判断非常关键。
三、滤波算法:滑动平均与中值滤波的结合
超声波回波信号天然存在噪声:垃圾袋表面的褶皱会导致声波散射,偶尔出现的电磁干扰会产生明显的跳变毛刺。如果直接用单次测量值做满溢判断,可能出现垃圾桶明明只有半满却误报满溢的情况。软件滤波是低成本提升稳定性的有效手段,其中滑动平均滤波和中值滤波各有适用场景。
滑动平均滤波的思路是维护一个固定长度的窗口,每次新数据入队、旧数据出队,取窗口内所有数据的算术平均值作为输出。它对随机噪声的抑制效果好,但对突发毛刺的响应较差,因为一个异常大值会被平均稀释而不是剔除。中值滤波则相反,它取窗口内排序后的中间值,能有效剔除脉冲型毛刺,但对连续小幅噪声的平滑能力弱。实际项目中推荐两者串联使用:先做中值滤波去毛刺,再做滑动平均平滑,代码实现如下:
struct FilterEngine {
private var medianWindow: [Double] = []
private var meanWindow: [Double] = []
private let windowSize = 5
mutating func process(_ value: Double) -> Double {
// 第一级:中值滤波,剔除毛刺
medianWindow.append(value)
if medianWindow.count > windowSize {
medianWindow.removeFirst()
}
let median = medianWindow.sorted()[medianWindow.count / 2]
// 第二级:滑动平均,平滑输出
meanWindow.append(median)
if meanWindow.count > windowSize {
meanWindow.removeFirst()
}
let mean = meanWindow.reduce(0, +) / Double(meanWindow.count)
return mean
}
}窗口大小需要权衡响应速度和平滑效果:窗口越大输出越平稳,但对垃圾快速堆满的响应越迟钝。对于垃圾桶这种变化缓慢的场景,窗口取5到9都比较合适。另外建议在滤波之前先做一次范围校验,直接丢弃明显超出物理可能的值(比如大于桶高或小于2厘米的读数,后者通常是声波打到传感器支架上的近端反射),这样能避免脏数据污染滤波窗口。
四、满溢判断与后台断线重连
拿到滤波后的稳定距离,满溢判断就很简单了:预先设定满溢阈值(如距离小于10厘米判定为满),连续N次低于阈值才触发报警,避免偶发波动误报。报警可以通过本地通知、上报服务器或推送给环卫人员等多种方式实现。
另一个工程上必须考虑的问题是BLE连接的稳定性。垃圾桶部署在户外,iPhone可能走出蓝牙范围或系统回收蓝牙资源。Core Bluetooth提供了状态恢复机制:在Info.plist中开启bluetooth-central后台模式,并在初始化CBCentralManager时传入restoreIdentifier,App被系统杀掉后蓝牙事件仍能唤醒应用。重连时优先使用retrievePeripherals(withIdentifiers:)根据已保存的UUID找回外设,比重新扫描更快速可靠。这套机制配合上述滤波与补偿方案,才能构成一个真正可用的智能垃圾桶监测客户端。
Core Bluetooth超声波传感器智能垃圾桶修改时间:2026-09-13 11:54:36