Expo(React Native) 物联网开发:关系、路径与常见坑

Created on

前言

物联网应用的核心不是 UI,而是“设备连接 + 网络可靠性 + 协议稳定性”。React Native 负责跨平台渲染与业务逻辑,Expo 负责工程化和常见原生能力封装。当这两者进入 IoT 场景,你会发现它们的价值和边界都变得更清晰。

这篇文章从关系、路径与常见坑出发,帮助你在 RN/Expo + IoT 的组合中少走弯路。

关系:RN 是运行时,Expo 是工程平台

在 IoT 项目里可以这样理解:

因此,Expo 不会改变 IoT 的复杂度,它只是让你更快进入业务问题本身。

路径:IoT 连接模型的三种主流方案

1) BLE 直连

适用于近场设备,常见于传感器、可穿戴、简单家居设备。特点是低功耗、低带宽、连接不稳定。

典型流程:

BLE 直连如何实现(Expo Prebuild 版)

说明:BLE 能力通常需要 Prebuild/Bare,Managed 模式可能不足。

  1. 安装依赖并预生成原生工程
npm i react-native-ble-plx
expo prebuild
  1. 配置权限
  1. 最小可运行逻辑(扫描/连接/读写)
import { useEffect, useMemo, useState } from "react";
import { Platform, PermissionsAndroid } from "react-native";
import { BleManager } from "react-native-ble-plx";

const SERVICE_UUID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
const WRITE_CHAR_UUID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";

export function useBleConnection() {
  const manager = useMemo(() => new BleManager(), []);
  const [deviceId, setDeviceId] = useState<string | null>(null);

  useEffect(() => {
    return () => manager.destroy();
  }, [manager]);

  async function ensurePermissions() {
    if (Platform.OS !== "android") return true;

    if (Platform.Version >= 31) {
      const scan = await PermissionsAndroid.request(
        PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN
      );
      const connect = await PermissionsAndroid.request(
        PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT
      );
      return scan === "granted" && connect === "granted";
    }

    const fine = await PermissionsAndroid.request(
      PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION
    );
    return fine === "granted";
  }

  async function scanAndConnect() {
    const ok = await ensurePermissions();
    if (!ok) return null;

    return new Promise<string | null>((resolve) => {
      const timeout = setTimeout(() => {
        manager.stopDeviceScan();
        resolve(null);
      }, 10000);

      manager.startDeviceScan([SERVICE_UUID], null, async (error, device) => {
        if (error || !device) return;

        manager.stopDeviceScan();
        clearTimeout(timeout);

        const connected = await device.connect();
        await connected.discoverAllServicesAndCharacteristics();

        setDeviceId(connected.id);
        resolve(connected.id);
      });
    });
  }

  async function write(dataBase64: string) {
    if (!deviceId) return;
    const device = await manager.connectToDevice(deviceId);
    await device.writeCharacteristicWithResponseForService(
      SERVICE_UUID,
      WRITE_CHAR_UUID,
      dataBase64
    );
  }

  return { scanAndConnect, write };
}
  1. 断线重连

2) 云端中转(MQTT/HTTP)

适用于联网设备,通过服务端中转指令与状态。特点是远程控制稳定,但引入服务端复杂度。

典型流程:

云端中转如何实现(MQTT 版)

说明:App 端用 MQTT 订阅/发布,设备端连接 Broker 上报与接收指令。

  1. 设备端接入 Broker
  1. 服务端职责
  1. App 端最小示例(订阅/发布)
import { useEffect, useRef } from "react";
import mqtt, { MqttClient } from "mqtt";

const BROKER_URL = "wss://broker.example.com:8083/mqtt";
const TOPIC_STATUS = "device/123/status";
const TOPIC_COMMAND = "device/123/command";

export function useMqtt() {
  const clientRef = useRef<MqttClient | null>(null);

  useEffect(() => {
    const client = mqtt.connect(BROKER_URL, {
      clientId: `app-${Date.now()}`,
      keepalive: 30,
      reconnectPeriod: 2000,
      clean: true,
    });

    client.on("connect", () => {
      client.subscribe(TOPIC_STATUS);
    });

    client.on("message", (_topic, payload) => {
      const message = payload.toString();
      console.log("device status", message);
    });

    clientRef.current = client;
    return () => client.end(true);
  }, []);

  function publishCommand(data: string) {
    clientRef.current?.publish(TOPIC_COMMAND, data, { qos: 1 });
  }

  return { publishCommand };
}
  1. 可靠性建议

3) 混合模式(BLE + 云)

最常见的商业路径:近场 BLE 做配网和配置,远程通过云端控制。

典型流程:

混合模式如何实现(配网 + 云控)

  1. BLE 配网
  1. 设备入云
  1. App 切换到云端控制
  1. 状态一致性策略

混合模式最小可运行示例(JSON + MQTT)

说明:BLE 配网使用 JSON 字符串,通过特征值写入;云端通信使用 MQTT。

  1. BLE 配网写入(示例)
import { BleManager } from "react-native-ble-plx";

const SERVICE_UUID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
const WRITE_CHAR_UUID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";

function toBase64(data: string) {
  return Buffer.from(data, "utf8").toString("base64");
}

export async function sendWifiConfig(
  manager: BleManager,
  deviceId: string,
  ssid: string,
  password: string
) {
  const payload = {
    ssid,
    password,
    timestamp: Date.now(),
  };

  const json = JSON.stringify(payload);
  const base64 = toBase64(json);

  const device = await manager.connectToDevice(deviceId);
  await device.discoverAllServicesAndCharacteristics();
  await device.writeCharacteristicWithResponseForService(
    SERVICE_UUID,
    WRITE_CHAR_UUID,
    base64
  );
}
  1. 设备端配网回执(约定)
  1. MQTT 主题设计(示例)
  1. App 端云控最小示例
import mqtt from "mqtt";

const BROKER_URL = "wss://broker.example.com:8083/mqtt";

export function connectMqtt(deviceId: string) {
  const client = mqtt.connect(BROKER_URL, {
    clientId: `app-${Date.now()}`,
    keepalive: 30,
    reconnectPeriod: 2000,
  });

  const statusTopic = `device/${deviceId}/status`;
  const commandTopic = `device/${deviceId}/command`;

  client.on("connect", () => {
    client.subscribe(statusTopic);
  });

  client.on("message", (_topic, payload) => {
    console.log("status", payload.toString());
  });

  function sendCommand(cmd: string) {
    client.publish(commandTopic, cmd, { qos: 1 });
  }

  return { client, sendCommand };
}
  1. 一致性处理建议

Expo 在 IoT 项目中的常见定位

Expo 的能力可以覆盖大部分 IoT App 的“外围问题”,但设备通信本身经常需要更深的原生能力:

简单判断:只要连接链路或设备 SDK 需要原生定制,就要提前准备进入 Prebuild 形态。

常见坑与现实限制

1) BLE 在真实环境下不稳定

实验室能连上,现场却经常断开。干扰、距离、电量、系统省电都会破坏连接稳定性。

建议:

2) 权限与系统策略导致“扫不到”

Android 不同版本权限差异大,iOS 也有蓝牙权限弹窗限制。

建议:

3) 后台与省电策略导致连接失效

IoT App 常被误认为需要“后台长连接”,但系统对后台连接限制非常严格。

建议:

4) 设备协议变更导致功能失效

固件升级、协议字段变更、服务 UUID 变化都会让 App 看似连接成功却无法操作。

建议:

5) MQTT 断线与消息顺序问题

移动网络波动会导致 MQTT 重连频繁,消息乱序或丢失。

建议:

工程实践建议

结语

React Native 与 Expo 是 IoT App 的效率引擎,但 IoT 的核心复杂度来自设备与网络。理解它们的分工,再把连接链路当作“不稳定输入”去设计,你就能把真正的稳定性交付给用户。