HARDWARE INTEGRATION

牧知 · 硬件对接指南

把智能耳标和圈舍环境传感器的数据接进平台。后端已就绪,设备端只要会发一个 HTTP POST 就能接入,前端页面会自动刷新,不用改任何一行网页代码。

正在检测后端… 在栏 — 读数 — 环境 — 待处理预警 —

1接入前准备

后端已经跑在 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

2个体数据上报

耳标侧最重要的接口:体温 + 活动量 + 行为分类结果。

POST /api/ingest/readings 需 X-API-Key

请求体:{ "items": [ … ] }, 一次最多 2000 条。网关可以攒一批再发,减少请求数。

字段类型范围说明
tagstring—耳标编号,如 NM-0138必填
temp_cfloat30 ~ 45体温 ℃必填
activityfloat0 ~ 40活动量,窗口内三轴加速度模值的统计量必填
tsint—unix 秒。不传则用服务器接收时间可选
stepsint≥ 0步数可选
behaviorstring6 类采食 / 反刍 / 站立 / 躺卧 / 行走 / 奔跑,默认「站立」可选
anomaly_probfloat0 ~ 1模型输出的异常概率,默认 0可选
✓
自动预警:上报的体温如果偏离该牛个体基线超过 0.7℃(约 2σ),后端会自动生成一条「发热」高级预警,6 小时内同一条牛不重复生成。你不需要自己判断,也不用自己写预警逻辑。

⚡ 在线试一发

3环境数据上报

圈舍里的温湿度、气体、光照传感器,一个圈舍一条记录。

POST /api/ingest/env 需 X-API-Key

请求体:单个对象,6 个字段全部必填。

字段类型范围说明
temperaturefloat-40 ~ 60环境温度 ℃
humidityfloat0 ~ 100相对湿度 %RH
methanefloat0 ~ 200甲烷 ppm
co2float0 ~ 10000二氧化碳 ppm
luxfloat0 ~ 200000光照强度 lux
windfloat0 ~ 60风速 m/s
tsint—unix 秒,不传用服务器时间可选

⚡ 在线试一发

4设备端代码

三种角度:先命令行验证,再 Python 网关,最后嵌入式 C。

⚠
nRF52832 不能直连:它只有 BLE,没有 TCP/IP 协议栈,不能直接发 HTTP。必须经过网关转发 —— 手机 App、ESP32、4G 模组(如 Air780E)都行。耳标 → BLE → 网关 → HTTP → 服务器,这一跳不能省。

① 命令行验证(最快)

先在服务器上 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}

② Python 网关(树莓派 / 工控机)

网关收到 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 Arduino(WiFi 直连)

如果网关用 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 要带证书:ESP32 连 https:// 默认会校验证书,证书链不全就失败。测试阶段可以 http.begin(url, CA_CERT) 传根证书,或者先用 http://(如果 Nginx 配了 80 端口跳转,注意别被 301 吃掉 POST —— 见排错表)。

5上报规则与频率

这几个数字决定了数据能不能扛住 1268 头牛的规模。

项目建议值理由
个体上报间隔5 ~ 10 分钟体温是缓变量,10 分钟足够;1268 头 × 10 分钟 ≈ 2.1 条/秒
环境上报间隔1 ~ 5 分钟温湿度变化慢,5 分钟一次足够画出平滑曲线
单次批量条数20 ~ 200上限 2000,但一次发太多失败代价大,建议 20~200
失败重试指数退避5s → 15s → 60s,别死循环打爆服务器
时间戳精度整分钟对齐前端按整点分桶画曲线,秒级抖动会让曲线出现毛刺
✓
容量不用担心:单行存储约 88 字节。1268 头牛 × 10 分钟一条 ≈ 8.2 万条/天,一年约 2.6 GB。SQLite 完全扛得住,2C2G 的机器也不用换 MySQL。
⚠
不要传原始波形:50Hz × 6 通道 × 24 小时 = 一头牛一天 2592 万个点,这个量级会立刻压垮 SQLite。原始波形留在耳标本地或网关 SD 卡,只上传特征值和分类结果。

6常见问题

按报错现象查,多数问题在三行命令内能定位。

现象原因怎么办
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 Gatewayuvicorn 挂了systemctl status cattle-api 看状态,journalctl -u cattle-api -n 50 看日志
写库很慢 / database is locked并发写冲突服务必须 --workers 1,SQLite 单写者,别开多进程