把智能耳标和圈舍环境传感器的数据接进平台。后端已就绪,设备端只要会发一个 HTTP POST 就能接入,前端页面会自动刷新,不用改任何一行网页代码。
后端已经跑在 127.0.0.1:8000,Nginx 把 /api/ 反代出来。设备只要访问公网域名即可。
浏览器打开 https://sheepcnn.xyz/api/health,能看到 JSON 就说明链路通了。本页顶部的状态条也会自动显示。
上报接口用 X-API-Key 请求头鉴权,默认值是 change-me。上线前务必改成自己的密钥:
# 编辑服务配置 vi /etc/systemd/system/cattle-api.service # 把这行的值换成你自己的(建议 24 位以上随机串) Environment=CATTLE_API_KEY=你的密钥 systemctl daemon-reload && systemctl restart cattle-api
后端有个保护机制:数据超过 3 小时没更新就重灌种子,方便演示。接真设备前必须关掉,否则会覆盖真实数据。同一个文件里再加一行:
Environment=CATTLE_AUTO_RESEED=0
上报的 tag 必须能在 cattle 表里查到,否则会被拒收。现有编号是 NM-0001 到 NM-1268。要新增牛只,用工具导入:
python3 db.py template cattle > new.csv # 生成模板 # 填好后导入 python3 db.py import cattle new.csv --yes
耳标侧最重要的接口:体温 + 活动量 + 行为分类结果。
请求体:{ "items": [ … ] },
一次最多 2000 条。网关可以攒一批再发,减少请求数。
| 字段 | 类型 | 范围 | 说明 |
|---|---|---|---|
| tag | string | — | 耳标编号,如 NM-0138必填 |
| temp_c | float | 30 ~ 45 | 体温 ℃必填 |
| activity | float | 0 ~ 40 | 活动量,窗口内三轴加速度模值的统计量必填 |
| ts | int | — | unix 秒。不传则用服务器接收时间可选 |
| steps | int | ≥ 0 | 步数可选 |
| behavior | string | 6 类 | 采食 / 反刍 / 站立 / 躺卧 / 行走 / 奔跑,默认「站立」可选 |
| anomaly_prob | float | 0 ~ 1 | 模型输出的异常概率,默认 0可选 |
圈舍里的温湿度、气体、光照传感器,一个圈舍一条记录。
请求体:单个对象,6 个字段全部必填。
| 字段 | 类型 | 范围 | 说明 |
|---|---|---|---|
| temperature | float | -40 ~ 60 | 环境温度 ℃ |
| humidity | float | 0 ~ 100 | 相对湿度 %RH |
| methane | float | 0 ~ 200 | 甲烷 ppm |
| co2 | float | 0 ~ 10000 | 二氧化碳 ppm |
| lux | float | 0 ~ 200000 | 光照强度 lux |
| wind | float | 0 ~ 60 | 风速 m/s |
| ts | int | — | unix 秒,不传用服务器时间可选 |
三种角度:先命令行验证,再 Python 网关,最后嵌入式 C。
先在服务器上 curl 一发,确认后端没问题,再动固件。
curl -X POST https://sheepcnn.xyz/api/ingest/readings \ -H "X-API-Key: change-me" \ -H "Content-Type: application/json" \ -d '{"items":[{"tag":"NM-0138","temp_c":39.3,"activity":2.1,"behavior":"躺卧"}]}' # 正常返回 # {"accepted":1,"rejected":[],"alerts_created":1}
# 环境数据 curl -X POST https://sheepcnn.xyz/api/ingest/env \ -H "X-API-Key: change-me" \ -H "Content-Type: application/json" \ -d '{"temperature":25.4,"humidity":58,"methane":7.2,"co2":720,"lux":260,"wind":1.2}' # {"ok":true}
网关收到 BLE 数据后攒批上传,断网时落本地,恢复后补传。
import time, json, sqlite3, urllib.request API = "https://sheepcnn.xyz/api/ingest/readings" KEY = "change-me" # 换成你的密钥 BATCH = 20 # 攒 20 条发一次 def post(items): req = urllib.request.Request( API, method="POST", data=json.dumps({"items": items}).encode(), headers={"Content-Type": "application/json", "X-API-Key": KEY}) with urllib.request.urlopen(req, timeout=10) as r: return json.loads(r.read()) buf = [] while True: pkt = read_from_ble() # 你的 BLE 读取逻辑 buf.append({ "tag" : pkt["tag"], "temp_c" : round(pkt["temp"], 2), "activity" : round(pkt["act"], 2), "behavior" : pkt["behavior"], }) if len(buf) >= BATCH: try: post(buf); buf = [] except Exception as e: print("上传失败,保留缓冲", e) time.sleep(30)
如果网关用 ESP32,直接用 HTTPClient,不用自己拼 socket。
#include <WiFi.h> #include <HTTPClient.h> const char* URL = "https://sheepcnn.xyz/api/ingest/readings"; const char* KEY = "change-me"; void upload(const char* tag, float temp, float act) { if (WiFi.status() != WL_CONNECTED) return; HTTPClient http; http.begin(URL); http.addHeader("Content-Type", "application/json"); http.addHeader("X-API-Key", KEY); char body[256]; snprintf(body, sizeof(body), "{\"items\":[{\"tag\":\"%s\",\"temp_c\":%.2f,\"activity\":%.2f}]}", tag, temp, act); int code = http.POST((uint8_t*)body, strlen(body)); Serial.printf("HTTP %d %s\n", code, http.getString().c_str()); http.end(); }
https:// 默认会校验证书,证书链不全就失败。测试阶段可以 http.begin(url, CA_CERT) 传根证书,或者先用 http://(如果 Nginx 配了 80 端口跳转,注意别被 301 吃掉 POST —— 见排错表)。这几个数字决定了数据能不能扛住 1268 头牛的规模。
| 项目 | 建议值 | 理由 |
|---|---|---|
| 个体上报间隔 | 5 ~ 10 分钟 | 体温是缓变量,10 分钟足够;1268 头 × 10 分钟 ≈ 2.1 条/秒 |
| 环境上报间隔 | 1 ~ 5 分钟 | 温湿度变化慢,5 分钟一次足够画出平滑曲线 |
| 单次批量条数 | 20 ~ 200 | 上限 2000,但一次发太多失败代价大,建议 20~200 |
| 失败重试 | 指数退避 | 5s → 15s → 60s,别死循环打爆服务器 |
| 时间戳精度 | 整分钟对齐 | 前端按整点分桶画曲线,秒级抖动会让曲线出现毛刺 |
按报错现象查,多数问题在三行命令内能定位。
| 现象 | 原因 | 怎么办 |
|---|---|---|
| 401 invalid X-API-Key | 密钥不对或没带 | 检查请求头名是 X-API-Key(不是 Authorization),值和 systemd 里一致 |
返回 "rejected":["NM-9999"] | 耳标不在库里 | python3 db.py rows cattle 5 看有哪些编号,或用 import 新增 |
| 422 Unprocessable Entity | 字段超范围或类型错 | 对照字段表的范围,体温 30~45、活动量 0~40 |
| 301 / 308 重定向 | 用了 http 或漏了结尾斜杠 | 上报地址用 https://sheepcnn.xyz/api/ingest/readings,前面带 /api |
| 数据传上去了,页面没变 | 前端有轮询间隔 | 环境 5 秒、曲线 60 秒刷新一次,或直接 Ctrl+F5 |
| 数据第二天消失了 | AUTO_RESEED 重灌覆盖 | systemd 加 CATTLE_AUTO_RESEED=0 后重启服务 |
| 502 Bad Gateway | uvicorn 挂了 | systemctl status cattle-api 看状态,journalctl -u cattle-api -n 50 看日志 |
| 写库很慢 / database is locked | 并发写冲突 | 服务必须 --workers 1,SQLite 单写者,别开多进程 |