大棚环境数据采集,为什么总在存储这一步翻车?

前两年帮一个种草莓的朋友看他的大棚监控,硬件部分做得挺漂亮:ESP32 挂 DHT22 测温湿度,光照传感器也接上了,数据每 30 秒稳定上报一次。结果我打开他那台放在棚边小屋的旧笔记本一看,MySQL 里 40 多万行数据,风扇响得像要起飞,一旦断电重启,服务还得手动拉起来。更离谱的是他写了个脚本定时清表,理由是"存不下"。

这其实是绝大多数大棚环境数据采集项目的通病。采集和上报往往不是难点,难的是数据落地以后的存储、维持和消费。你想想,一个大棚少说 5~10 个点位,每个点位每分钟一条,一年就是几百万条记录。自己买云主机、装数据库、写定时清理、处理磁盘告警、做备份,这些活儿跟"种好草莓"半毛钱关系都没有,却要吃掉你一大半精力。

常见的翻车姿势有这么几种:

  • 服务器成本失控:为了存时序数据租了高配云主机,结果 CPU 常年 5%,钱全花在闲置内存上。
  • 数据重复入库:设备网络抖动重发,同一条温湿度被写了三遍,报表统计直接失真。
  • 消费端重复处理:脚本崩了重启,没做已读标记,同一批数据被反复执行,该开的风机开了又关。
  • 运维断档:人一忙就忘了续费、忘了清盘、忘了看备份,出问题时数据已经丢了。

问题的本质是:大棚采集场景要的不是"一套完整的后端系统",而是一个稳定的"数据中转站"。设备只管往里扔,业务只管按顺序取,中间那堆存储、去重、队列、鉴权的脏活,不该由你亲自扛。

整体方案:设备推送 + 云端队列 + 业务读取的数据流

把上面的痛点拆开看,其实就三段:设备侧采集上报、中间层可靠暂存、业务侧按需消费。云递数据这类轻量级云端数据存储与分发平台的思路,就是把这中间层整个接过去,只留两个 HTTP 接口给你。

整体数据流是这样跑的:

  1. 设备侧:ESP8266/ESP32、树莓派、易语言写的采集程序,任何能发 HTTP 请求的东西,向推送接口 POST 一段 JSON。
  2. 云端队列:平台按写入顺序(FIFO)把数据排进队列,自动做规范化,并对内容完全相同的记录做哈希去重——同一条数据只保留第一条,重发不再产生脏数据。
  3. 业务侧:你的后台脚本、报表程序、手机端,调读取接口把数据取走。取走即标记已读,不会重复下发。

这个模型的好处是职责极其清晰。设备端不需要知道数据库长什么样,业务端不需要处理并发写入,双方通过一个"数据名(dataset)"解耦。你可以用 dataset=greenhouse_a_temp 存 A 棚温度,用 greenhouse_a_humid 存湿度,控制台里按数据名分类查看,条数、容量、已读状态一目了然。

下面这张表对比一下自建和用云端中转的差异,方便你判断自己该走哪条路:

对比项 自建服务器 + 数据库 云端队列中转
前期投入 买/租服务器、装 MySQL、配环境 注册拿 APIKey 即可
重复数据处理 需自己写去重逻辑 写入自动哈希去重
消费端一致性 需自己做已读标记 取走即标记已读,不重复下发
日常运维 磁盘、备份、告警全自理 控制台可视化查看与管理
数据可控性 完全自主 支持私有化部署,同样自主可控

落地步骤:从注册 APIKey 到第一行环境数据入库

说干就干。第一步是注册账号拿到 APIKey,这个在控制台里直接能看到。拿到之后,你就有了一个可以往里塞数据的"数据名空间"。

先别急着上硬件,用 curl 在电脑上跑通一次推送,确认链路没问题。这是最朴素的推送方式,请求体是 JSON:

curl -X POST "http://你的域名/api/push.php" \
  -H "Content-Type: application/json" \
  -d '{
    "api_key": "你的APIKey",
    "dataset": "greenhouse_a",
    "data": {
      "temp": 26.5,
      "humid": 68,
      "ts": "2025-01-01 10:00:00"
    }
  }'

跑通后返回成功,你再去控制台看,greenhouse_a 这个数据名下就有了一条记录。api_key 除了放在请求体里,也可以走 X-Api-Key 请求头,看你习惯。

接下来把这段逻辑搬到设备上。以 ESP32 为例,温湿度读出来后直接组 JSON 推上去,核心就是一次 HTTP POST:

#include <WiFi.h>
#include <HTTPClient.h>

const char* ssid     = "你的WiFi";
const char* password = "你的WiFi密码";

void pushEnvData(float temp, float humid) {
  if (WiFi.status() != WL_CONNECTED) return;

  HTTPClient http;
  http.begin("http://你的域名/api/push.php");
  http.addHeader("Content-Type", "application/json");

  String payload = "{\"api_key\":\"你的APIKey\","
                   "\"dataset\":\"greenhouse_a\","
                   "\"data\":{\"temp\":" + String(temp) +
                   ",\"humid\":" + String(humid) + "}}";

  int code = http.POST(payload);
  Serial.printf("push result: %d\n", code);
  http.end();
}

如果你手上是那种只发纯文本的老程序,比如某些易语言或按键精灵写的采集脚本,平台也兼容 ?api_key=xxx&dataset=yyy 加纯文本请求体的写法,直接存一段文本,不用改造成 JSON。这一点对存量设备特别友好。

一个建议:数据名不要图省事全塞一个 dataset。按棚、按传感器类型拆开,后面读取和排查会轻松很多。

读取与消费:按写入顺序取数、分页与按数据名读取

数据存进去了,业务端怎么取?读取接口是 POST http://你的域名/api/pull.php,核心行为是按写入顺序(FIFO)读取,取走即标记已读,不会重复下发。这正好解决了前面说的"脚本重启导致重复处理"的问题——取过的数据不会再来一遍。

最基本的读取,指定数据名即可:

curl -X POST "http://你的域名/api/pull.php" \
  -H "Content-Type: application/json" \
  -d '{
    "api_key": "你的APIKey",
    "dataset": "greenhouse_a"
  }'

一次取不完怎么办?接口支持分页。你的定时脚本可以每次取一批,处理完再取下一批,避免一次性拉太多把内存撑爆。处理逻辑大概是这个节奏:

import requests, time

API = "http://你的域名/api/pull.php"
KEY = "你的APIKey"

def consume():
    while True:
        resp = requests.post(API, json={
            "api_key": KEY,
            "dataset": "greenhouse_a"
        })
        items = resp.json().get("data", [])
        if not items:
            break
        for row in items:
            # 这里写你的业务:入库、告警、开风机……
            handle(row)
    time.sleep(30)

def handle(row):
    print("处理环境数据:", row)

注意几个细节。第一,取走即已读,所以你的 handle 逻辑要尽量幂等,万一处理中途抛异常,这条数据已经标记已读就取不回来了,重要的中间结果建议先落本地再处理。第二,按数据名读取意味着你可以让温度消费脚本只盯 greenhouse_a_temp,湿度告警脚本只盯 greenhouse_a_humid,互不干扰。第三,控制台里能直接看到每个数据名的条数和已读状态,脚本到底消费到哪了,一眼就能判断。

如果你更倾向于被动接收而不是轮询,注意这里的模型是队列式的,业务端按自己的节奏来取就行,不用被推送节奏绑架。

扩展与效果:控制台管理、私有化部署与接入更多设备

链路跑通之后,扩展其实很自然。控制台里可以对数据名做分类管理和标签备注,数据支持下载、编辑、清空、删除——比如你想把某段时间的温湿度导出做月度分析,直接下载就行,不用写 SQL。

接入更多设备几乎是零成本的。ESP8266、ESP32、树莓派、Python 脚本、PHP 网页、PowerShell 定时任务,任何能发 HTTP 请求的语言或设备都能接。一个大棚扩到十个大棚,无非是多几个数据名,你的业务消费脚本结构不用变。

如果你对数据落地位置有硬性要求——比如农业项目要求数据必须在自己内网——平台基于 Nginx + PHP + MySQL,支持私有化部署到自己的内网或公网服务器,数据完全自主可控。安全性上,API 密钥鉴权、PDO 预处理防注入、CSRF 防护、登录防爆破、接口限流这些都有覆盖。

成本这块也值得算一笔账。新用户注册即赠送 3 天会员免费试用,正式会员月卡 19 元、年卡 99 元,走卡密充值(YK 开头月卡、NK 开头年卡),多张可以叠加、到期顺延。比起为了存几百万条环境记录去养一台服务器,这个投入基本可以忽略。另外邀请好友注册,好友开月卡你获赠 10 天、开年卡获赠 30 天(天数以后台配置为准),每次续费都奖、自动到账,做农业社群的顺手就能攒不少时长。

实际效果就是:设备端一行 POST,业务端一个循环,中间的存储、去重、队列、鉴权、可视化全都交出去了。你要操心的重新回到"草莓棚温度超没超"这件事上。

常见问题

大棚环境数据采集一定要自己买服务器和数据库吗?

不一定。如果你的核心诉求是稳定地存下来、按顺序取出来,用云递数据这类云端数据存储与分发平台就够了,注册拿到 APIKey 就能通过 HTTP/JSON 接口存取数据,不用买服务器、不用装数据库。只有当你有明确的内网合规要求时,才考虑私有化部署,那也还是同一套接口。

ESP8266/ESP32 这类设备怎么把温湿度数据推送到云端?

设备读传感器拿到温湿度后,组一个 JSON 请求体,用 HTTP POST 发到推送接口 http://你的域名/api/push.php,带上 api_key、dataset 和 data 三个字段即可。ESP32 可以用 HTTPClient 库,ESP8266 用 ESP8266HTTPClient,本质都是一次普通 HTTP 请求,不需要额外 SDK。

云端的数据读取后会不会重复下发?

不会。读取接口按写入顺序(FIFO)取数,取走即标记为已读,同一条数据不会再次下发。这也意味着你的消费逻辑要尽量做到幂等,处理失败时靠本地临时存储兜底,而不是指望重新拉取。

会员到期后,之前采集的大棚数据会被删除吗?

不会删除。会员到期后只是暂停推送和读取,数据仍然保留,续费后即恢复使用。所以不用担心某段时间忘记续费导致历史环境数据丢失。

云递数据可以私有化部署到自己的内网服务器吗?

可以。平台基于 Nginx + PHP + MySQL,支持私有化部署到自己的内网或公网服务器,数据自主可控。对于有数据不出内网要求的农业或企业项目,这条路是通的,接口用法和云端一致。

开始使用

本文的代码示例都可以直接在云递数据上运行:注册后即可获得专属 APIKey,在 开发文档 中查看推送、读取接口的完整说明;需要长期使用可以在 会员中心 用卡密开通月卡或年卡;更多实战内容可以浏览技术博客的其他文章。