You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Native Expo App如何触发Raspberry Pi运行Python采集数据存入Firebase

实现方案

你现有技术栈完全可以直接实现需求,不需要额外加第三方服务、做内网穿透,核心直接用Firebase实时数据库做信令通道就行,整套逻辑跑通很简单,分三部分配:

树莓派端配置

  • 不要做“收到请求再启动Python脚本”的逻辑,延迟高还容易出环境、权限问题,直接写一个Python常驻脚本,配systemd开机自启,后台一直跑着。
  • 脚本里接Firebase Admin SDK,初始化后长期监听一个固定的信令节点,比如/commands/trigger_collect。
  • 监听到节点值变为True时,直接调用你已经写好的串口驱动Arduino采集传感器的逻辑就行,不用改原来的采集代码。
  • 采集完成后做三个操作:
    • 把带时间戳的传感器数据写入/sensor_data节点
    • 把/commands/trigger_collect的值重置为False,避免重复触发
    • 往/commands/last_run_status节点写入本次执行结果(成功/失败、错误信息、执行时间)
  • 注意给串口操作加异常捕获和超时,别因为Arduino响应异常把整个常驻脚本卡挂。

核心代码参考:

import firebase_admin
from firebase_admin import db
import serial
import time
from datetime import datetime

# 初始化Firebase服务账号
cred = firebase_admin.credentials.Certificate("你的服务账号密钥文件路径.json")
firebase_admin.initialize_app(cred, {'databaseURL': '你的Firebase实时数据库地址'})

# 初始化串口连接,端口按你实际的改
ser = serial.Serial('/dev/ttyACM0', 9600, timeout=2)
time.sleep(2) # 等串口握手完成

def run_collect():
    # 这里直接放你原来写的驱动Arduino读传感器的代码就行
    ser.write(b'start_collect\n')
    time.sleep(1)
    raw_data = ser.readline().decode().strip()
    return {"sensor_value": raw_data, "timestamp": int(time.time()*1000)}

def trigger_callback(event):
    if event.data == True:
        try:
            data = run_collect()
            # 写入采集到的传感器数据
            db.reference('/sensor_data').push(data)
            # 重置触发位
            db.reference('/commands/trigger_collect').set(False)
            # 写入成功状态
            db.reference('/commands/last_run_status').set({
                "code": 0,
                "msg": "collect success",
                "run_time": datetime.now().isoformat()
            })
        except Exception as e:
            # 异常时也要重置触发位,写错误状态
            db.reference('/commands/trigger_collect').set(False)
            db.reference('/commands/last_run_status').set({
                "code": -1,
                "msg": str(e),
                "run_time": datetime.now().isoformat()
            })

# 绑定信令节点监听
trigger_ref = db.reference('/commands/trigger_collect')
trigger_ref.listen(trigger_callback)

# 保持进程常驻
while True:
    time.sleep(1)
  • 脚本写完用systemd配成系统服务,设置异常自动重启,比rc.local、crontab稳很多,记得把运行脚本的用户加到dialout组,不然会报串口权限错误,不用拿root跑。

Expo移动端配置

  • 直接用你项目里已经集成的Firebase JS SDK就行,不需要加额外的原生依赖,不用改Expo的配置。
  • 采集按钮的点击事件里,直接把/commands/trigger_collect节点的值设为True,就完成了触发指令下发。
  • 页面挂载的时候监听/commands/last_run_status节点,收到执行成功的状态就提示用户,同时拉取最新的传感器数据展示就行;收到失败状态就把错误信息弹给用户。

核心代码参考:

import { getDatabase, ref, set, onValue } from "firebase/database";
import { Alert, Button, View } from "react-native";
import { useEffect } from "react";

const db = getDatabase();

export default function CollectPage() {
  const handleTrigger = async () => {
    // 触发前可以先查下当前触发位状态,防止重复点击
    try {
      await set(ref(db, 'commands/trigger_collect'), true);
      Alert.alert("提示", "采集指令已发送,等待结果")
    } catch (err) {
      Alert.alert("错误", "指令发送失败:" + err.message)
    }
  }

  useEffect(() => {
    const statusRef = ref(db, 'commands/last_run_status');
    const unsubscribe = onValue(statusRef, (snapshot) => {
      const status = snapshot.val();
      if(!status) return;
      if(status.code === 0) {
        Alert.alert("成功", "传感器数据采集完成")
        // 这里加逻辑拉取/sensor_data下的最新数据渲染
      } else if(status.code === -1) {
        Alert.alert("失败", "采集出错:" + status.msg)
      }
    })
    return () => unsubscribe();
  }, [])

  return <View><Button title="开始采集" onPress={handleTrigger} /></View>
}
  • 记得配Firebase数据库安全规则,不要开公开读写,移动端用项目里的Firebase鉴权做权限控制,限制只有登录的合法用户能写触发节点,树莓派端用Admin SDK走服务账号权限,不受安全规则限制。

可选优化

  • 采集任务多的话可以给/sensor_data节点配置数据保留策略,自动清理过期数据,省存储。
  • 可以给触发逻辑加锁,树莓派端收到触发信号后先判断当前是不是在采集中,避免并发操作串口报错。
  • 如果需要更高的可靠性,可以给信令加任务ID,移动端发指令的时候带唯一ID,树莓派端返回结果的时候带上对应ID,避免状态错乱。

这套方案完全复用你现有的三个组件,不需要额外买服务器、配公网穿透,Firebase的长连接本身稳定性足够,延迟一般在几百毫秒,完全满足远程触发的需求,同类树莓派远程采集项目实测连续跑半年没出过信令丢失的问题。

内容的提问来源于stack exchange,提问作者Panda

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.31 13:03:30