HarmonyOS开发:设备发现——设备扫描与配网

举报
Jack20 发表于 2026/06/25 21:23:14 2026/06/25
【摘要】 HarmonyOS开发:设备发现——设备扫描与配网📌 核心要点:设备发现是智能家居的第一步,WiFi扫描发现局域网设备,BLE扫描发现蓝牙设备,SoftAP和蓝牙配网让设备连上WiFi,配网成功率直接决定用户体验。 背景与动机用户买了个智能灯泡,拆开包装,装上灯座,打开你的App——然后呢?灯泡没连WiFi,App找不到灯泡,灯泡也连不上服务器。用户面对一个"未发现设备"的空页面,一脸懵...

HarmonyOS开发:设备发现——设备扫描与配网

📌 核心要点:设备发现是智能家居的第一步,WiFi扫描发现局域网设备,BLE扫描发现蓝牙设备,SoftAP和蓝牙配网让设备连上WiFi,配网成功率直接决定用户体验。

背景与动机

用户买了个智能灯泡,拆开包装,装上灯座,打开你的App——然后呢?

灯泡没连WiFi,App找不到灯泡,灯泡也连不上服务器。用户面对一个"未发现设备"的空页面,一脸懵。

这就是设备发现和配网要解决的问题。设备发现让App找到设备,配网让设备连上WiFi,绑定让设备和用户关联。 这三步走不通,后面什么控制、场景、定时全是白搭。

配网这事儿说起来简单:把WiFi密码告诉设备,设备连上WiFi,完事。但实际操作起来,坑多到你怀疑人生。2.4G和5G的兼容问题、路由器信道问题、设备热点连不上、蓝牙配网超时——每一个都是用户投诉的重灾区。

核心原理

设备发现流程

设备发现有两种主要方式:WiFi局域网扫描BLE蓝牙扫描

flowchart TD
    A[用户点击添加设备] --> B{选择发现方式}
    B -->|WiFi扫描| C[启动mDNS/SSDP扫描]
    B -->|BLE扫描| D[启动BLE广播扫描]
    
    C --> E[发现局域网设备]
    D --> F[发现蓝牙设备]
    
    E --> G{设备是否已配网?}
    F --> G
    
    G -->|已配网| H[直接绑定设备]
    G -->|未配网| I{选择配网方式}
    
    I -->|SoftAP配网| J[连接设备热点]
    I -->|蓝牙配网| K[通过BLE传输WiFi信息]
    
    J --> L[发送WiFi密码给设备]
    K --> L
    
    L --> M[设备连接WiFi]
    M --> N{连接成功?}
    N -->|| O[设备上线,完成绑定]
    N -->|| P[配网失败,重试]
    P --> I
    
    H --> O
    
    classDef scan fill:#1565C0,color:#fff,stroke:#0D47A1
    classDef discover fill:#2E7D32,color:#fff,stroke:#1B5E20
    classDef config fill:#E65100,color:#fff,stroke:#BF360C
    classDef result fill:#6A1B9A,color:#fff,stroke:#4A148C
    classDef error fill:#C62828,color:#fff,stroke:#B71C1C

    class A,B,C,D,scan
    class E,F,G,H,discover
    class I,J,K,L,M,config
    class O,result
    class P,error

WiFi扫描:mDNS和SSDP

mDNS(Multicast DNS) 是局域网设备发现的主流方案。设备在局域网内广播自己的服务信息(如_smartlight._tcp.local),App监听广播,发现设备。

SSDP(Simple Service Discovery Protocol) 是UPnP协议族的发现机制,原理类似,但报文格式不同。

大多数IoT设备使用mDNS,因为它更轻量,适合资源受限的嵌入式设备。

BLE扫描

BLE(低功耗蓝牙)扫描适合未配网的设备。设备上电后广播自己的存在(包含设备类型、MAC地址等信息),App扫描到广播包后,建立BLE连接进行配网。

BLE扫描的优势:不需要设备和手机在同一WiFi网络,甚至设备还没连WiFi就能发现。劣势:距离有限(通常10米以内),且Android/iOS的BLE API限制较多。

SoftAP配网

SoftAP配网的流程:设备开启WiFi热点 → 手机连接设备热点 → 手机通过HTTP把WiFi信息发给设备 → 设备关闭热点,连接WiFi。

这是最传统、兼容性最好的配网方式。缺点是用户体验差——需要手动切换WiFi连接,Android 10+还限制了后台WiFi切换。

蓝牙配网

蓝牙配网的流程:手机扫描到BLE设备 → 建立BLE连接 → 通过BLE通道把WiFi信息(SSID+密码)发给设备 → 设备连接WiFi。

体验更好,不需要切换WiFi,但要求设备支持BLE,且BLE传输速度有限(大密码或复杂加密可能超时)。

代码实战

基础用法:BLE设备扫描

用鸿蒙的@ohos.bluetooth.bleAPI扫描周围的BLE设备。

// services/DeviceDiscovery.ets
// 设备发现服务 - BLE扫描与WiFi扫描

import { ble } from '@kit.ConnectivityKit'
import { wifiManager } from '@kit.WifiKit'

// 发现的设备信息
interface DiscoveredDevice {
  deviceId: string       // 设备ID(MAC地址)
  deviceName: string     // 设备名称
  deviceType: string     // 设备类型(从广播数据解析)
  rssi: number           // 信号强度
  isConfigured: boolean  // 是否已配网
  discoverWay: 'ble' | 'wifi'  // 发现方式
}

class DeviceDiscoveryService {
  private bleScanner?: ble.BLEScanner
  private foundDevices: Map<string, DiscoveredDevice> = new Map()
  private scanTimeout: number = 30000  // 扫描超时30秒

  // 启动BLE扫描
  startBleScan(onDeviceFound: (device: DiscoveredDevice) => void): void {
    try {
      this.bleScanner = ble.createBLEScanner()

      // 配置扫描参数
      const scanConfig: ble.ScanOptions = {
        interval: 0,           // 扫描间隔,0表示连续扫描
        dutyMode: ble.ScanDuty.SCAN_MODE_LOW_LATENCY,  // 低延迟模式
        matchMode: ble.MatchMode.MATCH_MODE_AGGRESSIVE  // 宽松匹配,多发现设备
      }

      // 设置扫描过滤器 - 只扫描IoT设备的广播
      const filters: Array<ble.ScanFilter> = [
        {
          // 过滤厂商特定数据,0xXXXX是你们公司的厂商ID
          manufactureId: 0xXXXX,
          manufactureData: undefined,
          manufactureDataMask: undefined
        }
      ]

      // 监听扫描结果
      this.bleScanner.on('bleDeviceFind', (results: Array<ble.ScanResult>) => {
        results.forEach((result: ble.ScanResult) => {
          const device = this.parseScanResult(result)
          if (device && !this.foundDevices.has(device.deviceId)) {
            this.foundDevices.set(device.deviceId, device)
            onDeviceFound(device)
          }
        })
      })

      // 启动扫描
      this.bleScanner.startScan(filters, scanConfig)

      // 设置超时自动停止
      setTimeout(() => {
        this.stopBleScan()
      }, this.scanTimeout)

    } catch (err) {
      console.error(`BLE扫描启动失败: ${JSON.stringify(err)}`)
    }
  }

  // 解析BLE广播数据
  private parseScanResult(result: ble.ScanResult): DiscoveredDevice | null {
    try {
      const deviceId = result.deviceId
      const rssi = result.rssi

      // 从广播数据中解析设备信息
      // 实际项目中根据你们设备的广播协议解析
      let deviceName = '未知设备'
      let deviceType = 'unknown'
      let isConfigured = false

      // 遍历广播数据中的ServiceData
      if (result.serviceData && result.serviceData.length > 0) {
        const serviceData = result.serviceData[0]
        // 解析自定义广播协议
        const data = new Uint8Array(serviceData.serviceData.buffer)
        if (data.length >= 4) {
          deviceType = this.parseDeviceType(data[0])
          isConfigured = (data[1] & 0x01) === 1
        }
      }

      // 从localName获取设备名称
      if (result.deviceName) {
        deviceName = result.deviceName
      }

      return {
        deviceId,
        deviceName,
        deviceType,
        rssi,
        isConfigured,
        discoverWay: 'ble'
      }
    } catch (err) {
      return null
    }
  }

  // 解析设备类型码
  private parseDeviceType(typeCode: number): string {
    const typeMap: Record<number, string> = {
      0x01: 'light',
      0x02: 'switch',
      0x03: 'air-conditioner',
      0x04: 'curtain',
      0x05: 'sensor'
    }
    return typeMap[typeCode] || 'unknown'
  }

  // 停止BLE扫描
  stopBleScan(): void {
    if (this.bleScanner) {
      this.bleScanner.off('bleDeviceFind')
      this.bleScanner.stopScan()
      this.bleScanner = undefined
    }
  }
}

export default new DeviceDiscoveryService()

进阶用法:SoftAP配网

SoftAP配网是最传统的配网方式,兼容性最好。流程:连接设备热点 → 发送WiFi信息 → 等待设备连接WiFi。

// services/SoftApConfig.ets
// SoftAP配网服务

import { wifiManager } from '@kit.WifiKit'
import { http } from '@kit.NetworkKit'

// 配网结果
interface ConfigResult {
  success: boolean
  message: string
  deviceIp?: string
}

class SoftApConfigService {
  // 设备热点名称前缀,用于识别设备热点
  private readonly AP_PREFIX = 'SmartDevice_'

  // 执行SoftAP配网
  async configDevice(ssid: string, password: string): Promise<ConfigResult> {
    try {
      // 第一步:连接设备热点
      const connected = await this.connectToDeviceAp()
      if (!connected) {
        return { success: false, message: '无法连接设备热点' }
      }

      // 第二步:通过HTTP发送WiFi信息给设备
      const sent = await this.sendWifiInfoToDevice(ssid, password)
      if (!sent) {
        return { success: false, message: '发送WiFi信息失败' }
      }

      // 第三步:等待设备连接WiFi(设备会关闭热点)
      const deviceOnline = await this.waitForDeviceOnline(ssid)
      if (!deviceOnline) {
        return { success: false, message: '设备连接WiFi超时' }
      }

      return { success: true, message: '配网成功', deviceIp: '192.168.1.XXX' }
    } catch (err) {
      return { success: false, message: `配网异常: ${JSON.stringify(err)}` }
    }
  }

  // 连接设备热点
  private async connectToDeviceAp(): Promise<boolean> {
    return new Promise((resolve) => {
      try {
        // 获取扫描到的WiFi列表,找到设备热点
        const scanInfos = wifiManager.getScanInfoListSync()
        const deviceAp = scanInfos.find(info =>
          info.ssid.startsWith(this.AP_PREFIX)
        )

        if (!deviceAp) {
          resolve(false)
          return
        }

        // 连接设备热点(不需要密码或使用固定密码)
        const config: wifiManager.WifiDeviceConfig = {
          ssid: deviceAp.ssid,
          preSharedKey: '12345678',  // 设备热点默认密码
          securityType: wifiManager.WifiSecurityType.WIFI_SEC_TYPE_PSK
        }

        wifiManager.connectToDevice(config)
        resolve(true)
      } catch (err) {
        console.error(`连接设备热点失败: ${JSON.stringify(err)}`)
        resolve(false)
      }
    })
  }

  // 通过HTTP发送WiFi信息给设备
  private async sendWifiInfoToDevice(ssid: string, password: string): Promise<boolean> {
    return new Promise((resolve) => {
      try {
        const httpRequest = http.createHttp()
        // 设备热点的固定IP,通常是192.168.4.1或192.168.5.1
        const url = 'http://192.168.4.1/config'

        httpRequest.request(url, {
          method: http.RequestMethod.POST,
          header: { 'Content-Type': 'application/json' },
          extraData: {
            ssid: ssid,
            password: password,
            // 可以附加其他信息
            token: 'xxxx'  // 设备绑定token
          }
        }, (err, data) => {
          if (!err && data.responseCode === 200) {
            resolve(true)
          } else {
            resolve(false)
          }
          httpRequest.destroy()
        })
      } catch (err) {
        resolve(false)
      }
    })
  }

  // 等待设备连接到家庭WiFi后上线
  private async waitForDeviceOnline(ssid: string, timeout: number = 30000): Promise<boolean> {
    const startTime = Date.now()

    return new Promise((resolve) => {
      // 先切回家庭WiFi
      this.connectToHomeWifi(ssid)

      // 轮询检查设备是否上线
      const timer = setInterval(() => {
        if (Date.now() - startTime > timeout) {
          clearInterval(timer)
          resolve(false)
          return
        }

        // 实际项目中通过mDNS或云端API检查设备是否上线
        // 这里简化为检查WiFi是否连接成功
        const linkedInfo = wifiManager.getLinkedInfoSync()
        if (linkedInfo.ssid === ssid) {
          // WiFi已连接,等待设备上线
          // 实际项目中这里应该做设备上线检测
          clearInterval(timer)
          resolve(true)
        }
      }, 2000)
    })
  }

  // 连接家庭WiFi
  private connectToHomeWifi(ssid: string): void {
    try {
      const configs = wifiManager.getDeviceConfigsSync()
      const homeConfig = configs.find(c => c.ssid === ssid)
      if (homeConfig) {
        wifiManager.connectToDevice(homeConfig)
      }
    } catch (err) {
      console.error(`连接家庭WiFi失败: ${JSON.stringify(err)}`)
    }
  }
}

export default new SoftApConfigService()

完整示例:设备发现与配网页面

把BLE扫描、SoftAP配网、蓝牙配网整合到一个完整的设备添加流程中。

// pages/DeviceAddPage.ets
// 设备添加页面 - 扫描发现 + 配网绑定

import DeviceDiscoveryService, { DiscoveredDevice } from '../services/DeviceDiscovery'
import SoftApConfigService from '../services/SoftApConfig'
import { ble } from '@kit.ConnectivityKit'

// 配网步骤
type ConfigStep = 'scanning' | 'found' | 'configuring' | 'success' | 'failed'

@Entry
@Component
struct DeviceAddPage {
  @State step: ConfigStep = 'scanning'
  @State foundDevices: DiscoveredDevice[] = []
  @State selectedDevice: DiscoveredDevice | null = null
  @State wifiSsid: string = ''
  @State wifiPassword: string = ''
  @State configProgress: number = 0
  @State errorMessage: string = ''
  @State scanSeconds: number = 30

  private discoveryService = DeviceDiscoveryService
  private softApService = SoftApConfigService
  private scanTimer?: number

  aboutToAppear() {
    this.startScan()
    this.getCurrentWifi()
  }

  aboutToDisappear() {
    this.discoveryService.stopBleScan()
    if (this.scanTimer) {
      clearInterval(this.scanTimer)
    }
  }

  // 获取当前连接的WiFi信息
  getCurrentWifi() {
    try {
      const linkedInfo = wifiManager.getLinkedInfoSync()
      this.wifiSsid = linkedInfo.ssid
    } catch (err) {
      console.error('获取WiFi信息失败')
    }
  }

  // 启动扫描
  startScan() {
    this.step = 'scanning'
    this.foundDevices = []
    this.scanSeconds = 30

    this.discoveryService.startBleScan((device: DiscoveredDevice) => {
      this.foundDevices = [...this.foundDevices, device]
      // 发现设备后切换到found步骤
      if (this.step === 'scanning') {
        this.step = 'found'
      }
    })

    // 倒计时
    this.scanTimer = setInterval(() => {
      this.scanSeconds--
      if (this.scanSeconds <= 0) {
        clearInterval(this.scanTimer)
        if (this.foundDevices.length === 0) {
          this.errorMessage = '未发现设备,请确认设备已通电且在附近'
          this.step = 'failed'
        }
      }
    }, 1000)
  }

  // 选择设备并开始配网
  selectDevice(device: DiscoveredDevice) {
    this.selectedDevice = device

    if (device.isConfigured) {
      // 已配网设备直接绑定
      this.bindDevice(device)
    } else {
      // 未配网设备需要配网
      this.step = 'configuring'
      this.doConfig(device)
    }
  }

  // 执行配网
  async doConfig(device: DiscoveredDevice) {
    this.configProgress = 10

    try {
      // 根据设备能力选择配网方式
      // 优先蓝牙配网,体验更好
      if (device.discoverWay === 'ble') {
        this.configProgress = 30
        const result = await this.bleConfigDevice(device)
        this.configProgress = 80

        if (result) {
          this.configProgress = 100
          this.step = 'success'
        } else {
          // 蓝牙配网失败,回退到SoftAP
          this.configProgress = 30
          const softApResult = await this.softApService.configDevice(
            this.wifiSsid, this.wifiPassword
          )
          this.configProgress = 80

          if (softApResult.success) {
            this.configProgress = 100
            this.step = 'success'
          } else {
            this.errorMessage = softApResult.message
            this.step = 'failed'
          }
        }
      } else {
        // WiFi发现的设备用SoftAP配网
        const result = await this.softApService.configDevice(
          this.wifiSsid, this.wifiPassword
        )
        if (result.success) {
          this.configProgress = 100
          this.step = 'success'
        } else {
          this.errorMessage = result.message
          this.step = 'failed'
        }
      }
    } catch (err) {
      this.errorMessage = `配网异常: ${JSON.stringify(err)}`
      this.step = 'failed'
    }
  }

  // BLE配网
  async bleConfigDevice(device: DiscoveredDevice): Promise<boolean> {
    try {
      // 建立BLE连接
      const gattDevice: ble.GattDevice = {
        deviceId: device.deviceId
      }
      const gattClient = ble.createBLEGattClientDevice(gattDevice)
      await gattClient.connect()

      // 写入WiFi信息到特征值
      const wifiInfo = JSON.stringify({
        ssid: this.wifiSsid,
        password: this.wifiPassword
      })

      const characteristic: ble.BLECharacteristic = {
        serviceUuid: '0000XXXX-0000-1000-8000-00805F9B34FB',  // 自定义Service UUID
        characteristicUuid: '0000XXXX-0000-1000-8000-00805F9B34FB',  // 自定义特征UUID
        characteristicValue: stringToArrayBuffer(wifiInfo),
        descriptors: []
      }

      await gattClient.writeCharacteristicValue(characteristic, ble.GattWriteType.WRITE)

      // 等待设备连接WiFi
      await new Promise(resolve => setTimeout(resolve, 5000))

      gattClient.disconnect()
      return true
    } catch (err) {
      console.error(`BLE配网失败: ${JSON.stringify(err)}`)
      return false
    }
  }

  // 绑定设备(已配网设备)
  async bindDevice(device: DiscoveredDevice) {
    this.step = 'configuring'
    this.configProgress = 50

    try {
      // 调用后端API绑定设备到用户账号
      // 实际项目中是HTTP请求
      await new Promise(resolve => setTimeout(resolve, 1500))
      this.configProgress = 100
      this.step = 'success'
    } catch (err) {
      this.errorMessage = '设备绑定失败'
      this.step = 'failed'
    }
  }

  build() {
    Navigation() {
      Column() {
        // 根据步骤渲染不同UI
        if (this.step === 'scanning') {
          this.ScanView()
        } else if (this.step === 'found') {
          this.FoundView()
        } else if (this.step === 'configuring') {
          this.ConfiguringView()
        } else if (this.step === 'success') {
          this.SuccessView()
        } else {
          this.FailedView()
        }
      }
      .width('100%')
      .height('100%')
      .backgroundColor('#F8F8F8')
    }
    .title('添加设备')
    .titleMode(NavigationTitleMode.Mini)
  }

  // 扫描中视图
  @Builder ScanView() {
    Column() {
      // 扫描动画
      LoadingProgress()
        .width(80)
        .height(80)
        .color('#007DFF')

      Text('正在扫描附近设备...')
        .fontSize(16)
        .fontColor('#333333')
        .margin({ top: 24 })

      Text(`${this.scanSeconds}`)
        .fontSize(14)
        .fontColor('#999999')
        .margin({ top: 8 })

      // 提示信息
      Column() {
        Text('请确保:').fontSize(14).fontColor('#666666').fontWeight(FontWeight.Bold)
        Text('1. 设备已通电').fontSize(13).fontColor('#999999').margin({ top: 4 })
        Text('2. 设备在手机10米范围内').fontSize(13).fontColor('#999999').margin({ top: 2 })
        Text('3. 手机蓝牙已开启').fontSize(13).fontColor('#999999').margin({ top: 2 })
      }
      .alignItems(HorizontalAlign.Start)
      .margin({ top: 40 })
      .padding({ left: 40 })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
  }

  // 发现设备视图
  @Builder FoundView() {
    Column() {
      Text('发现以下设备')
        .fontSize(16)
        .fontColor('#333333')
        .margin({ top: 16, bottom: 12 })

      List({ space: 12 }) {
        ForEach(this.foundDevices, (device: DiscoveredDevice) => {
          ListItem() {
            Row() {
              Column() {
                Text(device.deviceName).fontSize(15).fontColor('#333333')
                Text(device.isConfigured ? '已配网' : '待配网')
                  .fontSize(12)
                  .fontColor(device.isConfigured ? '#4CAF50' : '#FF9800')
                  .margin({ top: 4 })
              }
              .alignItems(HorizontalAlign.Start)

              Blank()

              // 信号强度指示
              Text(`${device.rssi}dBm`)
                .fontSize(12)
                .fontColor('#999999')

              Image($r('sys.media.ohos_ic_public_arrow_right'))
                .width(16)
                .height(16)
                .fillColor('#CCCCCC')
                .margin({ left: 8 })
            }
            .width('100%')
            .padding(16)
            .backgroundColor('#FFFFFF')
            .borderRadius(12)
            .onClick(() => this.selectDevice(device))
          }
        })
      }
      .width('100%')
      .layoutWeight(1)
      .padding({ left: 16, right: 16 })

      // 继续扫描按钮
      Button('继续扫描')
        .width('90%')
        .height(44)
        .backgroundColor('#007DFF')
        .fontColor('#FFFFFF')
        .borderRadius(22)
        .margin({ bottom: 24 })
        .onClick(() => this.startScan())
    }
    .width('100%')
    .height('100%')
  }

  // 配网中视图
  @Builder ConfiguringView() {
    Column() {
      Progress({ value: this.configProgress, total: 100, type: ProgressType.Ring })
        .width(80)
        .height(80)
        .color('#007DFF')

      Text('正在配网...')
        .fontSize(16)
        .fontColor('#333333')
        .margin({ top: 24 })

      Text(`${this.configProgress}%`)
        .fontSize(14)
        .fontColor('#999999')
        .margin({ top: 8 })

      Text('请勿关闭此页面或切换到其他应用')
        .fontSize(13)
        .fontColor('#FF9800')
        .margin({ top: 24 })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
  }

  // 成功视图
  @Builder SuccessView() {
    Column() {
      Image($r('sys.media.ohos_ic_public_ok'))
        .width(64)
        .height(64)
        .fillColor('#4CAF50')

      Text('配网成功!')
        .fontSize(18)
        .fontColor('#333333')
        .fontWeight(FontWeight.Bold)
        .margin({ top: 24 })

      Text(this.selectedDevice?.deviceName || '')
        .fontSize(14)
        .fontColor('#666666')
        .margin({ top: 8 })

      Button('完成')
        .width('90%')
        .height(44)
        .backgroundColor('#007DFF')
        .fontColor('#FFFFFF')
        .borderRadius(22)
        .margin({ top: 40 })
        .onClick(() => {
          // 返回主页面
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
  }

  // 失败视图
  @Builder FailedView() {
    Column() {
      Image($r('sys.media.ohos_ic_public_fail'))
        .width(64)
        .height(64)
        .fillColor('#F44336')

      Text('配网失败')
        .fontSize(18)
        .fontColor('#333333')
        .fontWeight(FontWeight.Bold)
        .margin({ top: 24 })

      Text(this.errorMessage)
        .fontSize(14)
        .fontColor('#666666')
        .margin({ top: 8 })
        .textAlign(TextAlign.Center)
        .padding({ left: 40, right: 40 })

      Row() {
        Button('重试')
          .width('40%')
          .height(44)
          .backgroundColor('#007DFF')
          .fontColor('#FFFFFF')
          .borderRadius(22)
          .onClick(() => this.startScan())

        Button('返回')
          .width('40%')
          .height(44)
          .backgroundColor('#F5F5F5')
          .fontColor('#333333')
          .borderRadius(22)
          .onClick(() => {})
      }
      .width('90%')
      .justifyContent(FlexAlign.SpaceBetween)
      .margin({ top: 40 })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
  }
}

// 字符串转ArrayBuffer
function stringToArrayBuffer(str: string): ArrayBuffer {
  const encoder = new util.TextEncoder()
  return encoder.encodeInto(str)
}

import { util } from '@kit.ArkTS'
import { wifiManager } from '@kit.WifiKit'

踩坑与注意事项

1. 5G WiFi的坑

大量IoT设备只支持2.4G WiFi,不支持5G。但用户家里的路由器通常是2.4G/5G双频合一,手机连着5G,设备只认2.4G。SoftAP配网时,手机把当前WiFi的SSID发给设备,但这个SSID在设备看来可能搜不到(因为设备只扫描2.4G频段)。

解法: 配网前检测当前WiFi频段,如果是5G,提示用户切换到2.4G。或者在路由器设置里把2.4G和5G分成不同SSID。更优雅的方案是在配网页面让用户手动选择WiFi并输入密码,而不是自动读取当前WiFi。

2. BLE扫描的权限问题

BLE扫描需要位置权限(ohos.permission.LOCATION)和蓝牙权限(ohos.permission.ACCESS_BLUETOOTH)。HarmonyOS对权限管控很严格,没有权限直接调用API会抛异常。

更坑的是,即使有权限,Android 12+和HarmonyOS 4+对后台BLE扫描也有限制——App在后台时扫描频率会被大幅降低,甚至完全停止。

解法: 配网页面保持前台运行,不要让App进后台。在页面上加"请勿离开此页面"的提示。

3. SoftAP配网的WiFi切换问题

SoftAP配网需要手机先连设备热点,再切回家庭WiFi。这个过程在Android 10+被限制——App不能自动切换WiFi连接,必须用户手动在系统设置里切换。

解法: 引导用户手动切换WiFi。在配网页面显示步骤指引:“请打开系统WiFi设置 → 连接’SmartDevice_XXXX’热点 → 返回此页面继续配网”。虽然体验差,但兼容性最好。

4. 配网超时与重试

配网不是100%成功的。设备热点连不上、WiFi密码传输失败、设备连WiFi超时——每个环节都可能失败。配网超时设多少?30秒?60秒?设太短用户以为失败了,设太长用户等得不耐烦。

解法: 分阶段超时。连接设备热点5秒超时,发送WiFi信息3秒超时,等待设备上线20秒超时。哪个阶段失败就重试哪个阶段,不要从头来。最多重试3次,3次都失败就提示用户检查设备和WiFi。

5. 设备广播数据解析

不同厂商的BLE广播数据格式不一样,甚至同一厂商的不同产品线也不一样。你不能假设广播数据的格式是固定的。

解法: 定义一套自己的广播协议,让所有接入你平台的设备都按这个协议广播。协议要预留扩展字段,方便后续加新设备类型。如果接入第三方设备,写适配层做协议转换。

HarmonyOS 6适配说明

HarmonyOS 6对设备发现和配网做了几项改进:

  1. BLE扫描API增强:新增ble.ScanFilterserviceSolicitationUuid字段,支持按Service Solicitation UUID过滤,发现特定服务的设备更精准。

  2. WiFi配网简化:新增wifiManager.startEapolConfig()API,支持EAPOL协议配网,部分设备可以不用SoftAP,直接通过WiFi帧传输配网信息,用户体验更好。

  3. NFC碰一碰配网:HarmonyOS 6增强了NFC能力,支持NFC标签触发设备发现和配网流程。用户手机碰一下设备上的NFC标签,自动弹出配网页面,省去了手动扫描的步骤。

  4. 配网状态通知:新增wifiManager.on('deviceConfigChange')事件,监听配网状态变化,不需要轮询。

适配代码示例:

// HarmonyOS 6 NFC碰一碰配网
import { tag } from '@kit.ConnectivityKit'

// 在Ability的onNewWant中处理NFC标签
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  if (want.parameters?.['tagTech'] === 'NfcA') {
    // 从NFC标签中读取设备信息
    const nfcData = want.parameters?.['nfcData'] as string
    const deviceInfo = JSON.parse(nfcData)

    // 自动跳转到配网页面
    // deviceInfo包含设备类型、MAC地址等信息
    // 可以直接发起BLE连接进行蓝牙配网
  }
}

总结

设备发现和配网是智能家居的"第一公里"。这步走不通,后面全白搭。

BLE扫描发现设备,SoftAP和蓝牙配网让设备连WiFi,绑定让设备和用户关联。流程不复杂,但坑多——5G兼容、权限、WiFi切换、超时重试,每个都是用户投诉的高发区。

维度 评价
学习难度 ⭐⭐⭐⭐ BLE和WiFi的API不难,但配网流程的各种边界情况处理很复杂
使用频率 ⭐⭐⭐⭐⭐ 每个新设备都需要配网,是智能家居App的必经之路
重要程度 ⭐⭐⭐⭐⭐ 配网成功率直接影响用户留存,配网体验差=用户流失

记住:配网体验决定了用户对App的第一印象。 第一印象差了,后面做得再好用户也不给你机会。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。