HarmonyOS APP开发---"智泊"智能停车App,需要用到这个库
做一个智能停车 App,停车场每个车位装一个 CoAP 传感器,手机要组播发现所有车位、查询空位状态。
@ohos/coap的客户端能力 + 组播支持让车位状态实时可见,UDP 通信省电又低延迟。
📦 仓库地址:gitcode.com/CPF-Applica... | 安装:ohpm install @ohos/coap
写在前面
"智泊"的核心场景:停车场有 200 个车位,每个车位装一个 CoAP 传感器(地磁或红外检测有无车辆)。用户打开 App,要看到整个停车场的空位分布,点某个车位看详细信息。
为什么不用 HTTP?
- 传感器是电池供电的低功耗 MCU,HTTP 的 TCP 三次握手 + 几百字节 header 太浪费
- 200 个车位逐个 HTTP 轮询,网络和电量都扛不住
- 停车场是局域网环境,不需要走公网
CoAP 基于 UDP,报文头只有 4 字节,复用 REST 语义,还支持组播------一次广播请求,所有传感器各自响应。这正是"智泊"需要的。
@ohos/coap 基于 libcoap v4.3.1,通过 NAPI 把 CoAP 客户端和服务端能力暴露给 ArkTS。
这篇文章聊什么
- 组播发现车位------一次请求获取全部车位状态
- 单播查询详情------点击某车位查详细信息
- 工程实践------并发实例管理、日志与隐私、内存监控
第一步:安装与权限
bash
ohpm install @ohos/coap
json
"requestPermissions": [
{ "name": "ohos.permission.INTERNET" },
{ "name": "ohos.permission.GET_NETWORK_INFO" }
]
第二步:组播发现全部车位
一次组播请求,200 个传感器各自响应自己的状态:
typescript
import {
CoapClient, CoapRequestMethod, CoapRequestType,
CoapResponse, CoapResponseCode, CoapBlockMode,
} from '@ohos/coap'
interface SpotInfo {
id: string // 车位编号,如 A-023
occupied: boolean // 是否占用
section: string // 区域,如 A 区
sensorIp: string // 传感器 IP,用于后续单播查询
}
@Entry
@Component
struct ParkingMapPage {
@State spots: SpotInfo[] = []
@State scanning: boolean = false
// 组播发现所有车位
async discoverSpots() {
this.scanning = true
this.spots = []
// 每个请求创建独立实例(不可复用)
const client = new CoapClient()
client.setBlockMode(CoapBlockMode.COAP_BLOCK_USE_LIBCOAP)
try {
const data: CoapResponse = await client.request(
'coap://224.0.1.187:5683/parking/spot', // CoAP 组播地址
CoapRequestMethod.GET,
CoapRequestType.COAP_MESSAGE_NON, // 组播用 NON(无需确认)
)
if (data.code === CoapResponseCode.SUCCESS) {
const spot: SpotInfo = JSON.parse(data.message)
this.spots.push(spot)
}
} catch (err) {
console.error('组播发现失败: ' + JSON.stringify(err))
} finally {
this.scanning = false
}
}
build() {
Column() {
Button(this.scanning ? '扫描中...' : '扫描空车位')
.onClick(() => this.discoverSpots())
.enabled(!this.scanning)
// 停车场地图:按区域分组展示
Text(`空闲 ${this.spots.filter((s) => !s.occupied).length} / ${this.spots.length}`)
.fontSize(18).fontWeight(FontWeight.Bold).margin(12)
Grid() {
ForEach(this.spots, (spot: SpotInfo) => {
GridItem() {
Text(spot.id)
.fontSize(12)
.fontColor(spot.occupied ? '#999' : '#FFF')
.backgroundColor(spot.occupied ? '#EEE' : '#4ECDC4')
.width(56).height(56)
.textAlign(TextAlign.Center)
.borderRadius(6)
}
.onClick(() => this.querySpotDetail(spot))
}, (spot: SpotInfo) => spot.id)
}
.columnsTemplate('1fr 1fr 1fr 1fr 1fr')
.layoutWeight(1)
}
.width('100%').height('100%')
}
}
为什么用 COAP_MESSAGE_NON:组播场景下传感器不需要逐个确认(否则 200 个确认包会拥塞网络),NON(Non-confirmable)是组播的标准选择。
第三步:单播查询车位详情
用户点击某个车位,单播查询详细信息(占用时长、是否可预约等):
typescript
// 查询单个车位详情
async querySpotDetail(spot: SpotInfo) {
// ✅ 每次查询创建独立实例,避免并发状态冲突
const client = new CoapClient()
client.setBlockMode(CoapBlockMode.COAP_BLOCK_USE_LIBCOAP)
try {
const data = await client.request(
`coap://${spot.sensorIp}:5683/parking/spot/${spot.id}`,
CoapRequestMethod.GET,
CoapRequestType.COAP_MESSAGE_CON, // 单播用 CON(需要确认,保证可靠)
)
if (data.code === CoapResponseCode.SUCCESS) {
const detail = JSON.parse(data.message)
this.showSpotDialog(detail)
} else {
promptAction.showToast({ message: '车位信息获取失败' })
}
} catch (err) {
console.error('查询失败: ' + JSON.stringify(err))
promptAction.showToast({ message: '传感器无响应' })
}
}
并发查询的正确姿势------用户快速点击多个车位时,为每个请求创建独立实例:
typescript
// ❌ 危险:复用同一实例发并发请求,内部状态会冲突
const sharedClient = new CoapClient()
selectedSpots.forEach((spot) => {
sharedClient.request(`coap://${spot.sensorIp}/...`, ...) // 状态互相干扰
})
// ✅ 安全:每个请求独立实例
async function queryBatch(spots: SpotInfo[]): Promise<Array<SpotInfo>> {
const results: Array<SpotInfo> = []
// 控制并发数,避免瞬间创建过多实例
const CONCURRENCY = 5
for (let i = 0; i < spots.length; i += CONCURRENCY) {
const batch = spots.slice(i, i + CONCURRENCY)
const batchResults = await Promise.all(
batch.map(async (spot) => {
const client = new CoapClient() // 独立实例
try {
const data = await client.request(
`coap://${spot.sensorIp}:5683/parking/spot/${spot.id}`,
CoapRequestMethod.GET,
CoapRequestType.COAP_MESSAGE_CON,
)
return data.code === CoapResponseCode.SUCCESS ? JSON.parse(data.message) : null
} catch (e) {
return null
}
})
)
results.push(...batchResults.filter((r) => r !== null))
}
return results
}
第四步:App 作为服务端接收传感器主动推送
车位状态变化时(有车停入/驶离),传感器主动 POST 到手机,App 实时更新地图:
typescript
import { CoapServer } from '@ohos/coap'
const server = new CoapServer()
// 注册 POST 处理器:接收车位状态变更推送
server.handle('parking/update', (request) => {
try {
const update = JSON.parse(request.payload)
console.info(`车位 ${update.id} 状态变更: ${update.occupied ? '占用' : '空闲'}`)
// 更新本地状态,UI 自动刷新
this.spots = this.spots.map((s) =>
s.id === update.id ? { ...s, occupied: update.occupied } : s
)
return { code: '2.04', payload: 'OK' } // Changed
} catch (err) {
return { code: '4.00', payload: 'Bad Request' }
}
})
// 注册 GET 处理器:传感器查询 App 侧配置(如轮询间隔)
server.handle('parking/config', (request) => {
return {
code: '2.05', // Content
payload: JSON.stringify({ pushInterval: 5, heartbeat: 60 }),
}
})
server.start('coap://0.0.0.0:5683')
console.info('CoAP 服务端已启动,等待传感器推送')
// 页面销毁时停止服务端
aboutToDisappear() {
server.stop()
}
第五步:工程实践要点
1. Native 日志的开关管理
setNativeLogOpen(true) 能打印 libcoap 内部报文,调试协议问题时非常有用,但日志里会包含设备 IP 等信息:
typescript
// 开发阶段:开启,方便排查协议问题
if (BuildProfile.DEBUG) {
CoapClient.setNativeLogOpen(true)
}
// 生产环境:必须关闭,避免隐私信息进日志
// 上线前检查这一行!
CoapClient.setNativeLogOpen(false)
建议:把日志开关绑定到构建配置(DEBUG/RELEASE),避免上线时忘记关。
2. 长时间运行的内存监控
停车场监控页可能一直开着,CoAP 长连接场景要关注内存趋势:
typescript
// 定期记录内存占用,观察是否持续增长
setInterval(() => {
const mem = getMemoryUsage()
console.info(`内存占用: ${mem}MB,车位连接数: ${this.spots.length}`)
}, 60000)
如果发现内存持续增长,检查是否有未释放的 CoapClient 实例或未完成清理的服务端 handler。
3. 弱网与超时处理
停车场地下信号差,请求可能超时:
typescript
async requestWithRetry(url: string, maxRetry = 2): Promise<CoapResponse | null> {
for (let attempt = 0; attempt <= maxRetry; attempt++) {
const client = new CoapClient() // 每次重试新实例
try {
const data = await client.request(url, CoapRequestMethod.GET, CoapRequestType.COAP_MESSAGE_CON)
if (data.code === CoapResponseCode.SUCCESS) return data
} catch (err) {
console.warn(`第 ${attempt + 1} 次请求失败: ${JSON.stringify(err)}`)
if (attempt < maxRetry) {
await new Promise((r) => setTimeout(r, 500 * (attempt + 1))) // 退避重试
}
}
}
return null
}
为什么"智泊"选了 CoAP?
| 维度 | HTTP | MQTT | CoAP |
|---|---|---|---|
| 传输层 | TCP(重) | TCP | ✅ UDP(轻) |
| 报文头 | 几百字节 | 2 字节 | ✅ 4 字节 |
| 组播发现 | ❌ 不支持 | ❌ 不支持 | ✅ 原生支持 |
| 需要中间服务器 | 是 | 需要 Broker | ✅ 点对点直连 |
| 传感器功耗 | 高 | 中 | ✅ 低 |
| 设备当服务端 | 难 | 难 | ✅ 原生支持 |
关键差异 :CoAP 的组播能力 和设备可直接当服务端,让"智泊"不需要在停车场部署中间服务器------手机和传感器点对点通信即可。
总结
"智泊"这个场景里,@ohos/coap 解决了三件事:
- 组播发现------一次请求发现 200 个车位传感器,比逐个轮询高效百倍
- 双向能力------手机既能当客户端查询,也能当服务端接收传感器主动推送
- 低功耗------UDP + 4 字节报文头,电池供电的传感器能跑几年
如果你也在做智能家居、农业监测、工业传感器等 IoT 场景,@ohos/coap 让鸿蒙设备和受限设备之间的通信轻得像呼吸。