Skip to main content
note

SenseCAP 平台正式更名为 SenseCraft 数据平台!

使用 XIAO ESP32C3 连接到 SenseCraft 数据平台获取种植建议

在此期间,Seeed Studio 的 SenseCraft 数据平台开发并发布了新的 AI 功能。目前 SenseCraft 数据平台上 AI 顾问的主要功能专注于为种植者提供建设性的种植建议,并将在不久的将来更新更丰富的 AI 功能!

本教程将连接 XIAO ESP32 系列与 SenseCraft 数据平台,详细介绍如何使用 XIAO 和 Grove 系列传感器将数据上传到平台,并基于这些传感器数值从 AI 获得建设性建议。

入门指南​

硬件准备​

本教程中将使用的三个硬件是 XIAO ESP32C3、Grove Base for XIAO 和 Grove SHT40 温湿度传感器。为了方便接线,我们使用 XIAO 扩展板,您可以根据实际需要进行购买。

Seeed Studio XIAO ESP32C3Grove Base for XIAOGrove - 温湿度传感器(SHT40)

软件准备​

如果这是您第一次使用 XIAO ESP32C3,那么您需要先阅读这个 Wiki 来学习如何在 Arduino 中配置一个良好的开发环境。

此外,本教程也兼容 XIAO ESP32S3,您也可以使用 XIAO ESP32S3 来完成本教程的内容。

由于使用了 SHT40 传感器,您还需要在 Arduino 中添加以下两个库,以确保程序能够顺利运行。

XIAO ESP32C3 获取温湿度数据​

如下图所示,请将 Grove SHT40 温湿度传感器连接到 XIAO 的 IIC 接口。

然后为 XIAO ESP32C3 上传以下程序来驱动 SHT40 传感器工作,并开始获取空气中的温湿度值。

#include <Arduino.h>
#include <SensirionI2CSht4x.h>
#include <Wire.h>

SensirionI2CSht4x sht4x;

void setup() {

Serial.begin(115200);
while (!Serial) {
delay(100);
}

Wire.begin();

uint16_t error;
char errorMessage[256];

sht4x.begin(Wire);

uint32_t serialNumber;
error = sht4x.serialNumber(serialNumber);
if (error) {
Serial.print("Error trying to execute serialNumber(): ");
errorToString(error, errorMessage, 256);
Serial.println(errorMessage);
} else {
Serial.print("Serial Number: ");
Serial.println(serialNumber);
}
}

void loop() {
uint16_t error;
char errorMessage[256];

delay(1000);

float temperature;
float humidity;
error = sht4x.measureHighPrecision(temperature, humidity);
if (error) {
Serial.print("Error trying to execute measureHighPrecision(): ");
errorToString(error, errorMessage, 256);
Serial.println(errorMessage);
} else {
Serial.print("Temperature:");
Serial.print(temperature);
Serial.print("\t");
Serial.print("Humidity:");
Serial.println(humidity);
}
}

打开Arduino IDE的串口监视器,选择波特率为115200并观察结果。

SenseCraft数据平台HTTPS API介绍 -- 上传传感器数据​

现在我们知道了如何从SHT40传感器获取数据,让我们开始学习SenseCraft数据平台的以下API调用规则。您可以通过点击下面的按钮直接跳转到SenseCraft文档中心来了解如何使用SenseCraft数据平台的API。

SenseCraft数据平台接收传感器数据的基本原理是使用EUI、Key作为认证信息,通过POST方式上报设备数据。

HTTPS服务器地址:

https://sensecap.seeed.cc/deviceapi

关于 Header​

在 POST 中,您需要在 Header 中添加认证信息,这是设备的 EUI 和 Key 的 base64 加密数据,采用以下基本格式。

authorization = Device base64(EUI:Key)

关于接口​

设备用于上报传感器数据的服务器路径为:/kit/message_uplink,模式为 POST,以下请求参数可用且被允许。

名称类型描述
- requestIdstring设备端每次上报数据时生成的 uuidv4,确保每条消息的值都不同。
- timestampstring消息发送时的毫秒时间戳。
- intentstring当前固定为 "event"。
- deviceEuistring设备 EUI。
- deviceKeystring设备密钥。
- events[object]上报数据采集和设备状态的事件数组。
-- namestring事件名称。
-- value[object]事件值。
-- timestampstring数据采集时的毫秒时间戳。

以下是发送传感器上传数据的示例。

{
"requestId": "aaaa-aaaa-aaaa-aaaa",
"timestamp": "1691026791405",
"intent": "event",
"deviceEui": "2CF7xxxxxxx00002",
"deviceKey": "38xxxxxxxxxxxxxxxxxxxxC0EE76C3CD",
"events": [
{
"name": "measure-sensor",
"value": [
{
"channel": "1",
"measurements": {
"4097": "31.38",
"4098": "59.60"
},
"measureTime": "1691026791405"
}
]
},
{
"name": "update-channel-info",
"value": [
{
"channel": "1",
"sensorType": "1001",
"status": "normal"
}
],
"timestamp": "1691026791405"
}
]
}

上传温湿度数据到 SenseCraft 数据平台​

一旦我们理解了上述规则,我们就可以开始编写 HTTPS 程序,将我们的 SHT40 温湿度数据上传到 SenseCraft 数据平台。

步骤 1. 注册并登录 SenseCraft 数据平台​

您可以点击下面的链接直接进入 SenseCraft 数据平台国际站。如果这是您第一次使用 SenseCraft 的服务,您可能需要注册一个账户。

登录 SenseCraft 数据平台后会进入控制台界面。我们需要添加一个自己的套件,请点击左侧菜单栏中的 DevelopKit。

然后点击左上角的 Create DevelopKit。

然后只需选择 MIG Develop Kit 并点击 Confirm 按钮。

接下来,您将能够在仪表板中看到您创建的设备,通常是第一个。点击该设备的"Connect"按钮将显示该设备的 EUI 和 KEY 信息。请保存它们,我们将在接下来的步骤中使用它们。

步骤 2. 获取认证信息​

SenseCraft 的认证信息接口需要基于 Base64 的 EUI:KEY 加密。

例如,您的 EUI 是 2CF7F11003900000,Key 是 06C42483D7155E7006C42483D7155E70。那么您可以通过终端中的以下命令获取 Base64 加密的认证信息。

echo -n 2CF7F11003900000:06C42483D7155E7006C42483D7155E70 |base64

>>> MkNGN0YxMTAwMzkwMDAwMDowNkM0MjQ4M0Q3MTU1RTcwMDZDNDI0ODNENzE1NUU3MA==

请保留认证信息,我们稍后将在程序中将其用作 apiKey。

步骤 3. 获取传感器类型编号​

上传中包含了我们要报告的传感器类型编号和传感器名称。这样 SenseCraft 就知道我们从哪个传感器上传数据以及数据的单位是什么。

对于这一部分,请参考 SenseCraft 文档中心提供的传感器和数值编号对照表。

如何使用这两个文档,我们将以本文中使用的 SHT40 传感器为例,为您介绍说明。SHT40 传感器是一个可以同时测量温度和湿度数据的传感器。所以它有一个传感器类型的代码和两个(温度、湿度)数值的代码。

传感器类型的代码我们需要在传感器类型列表中查找。我们找到了一个温湿度传感器,代码为 1001。

我们所说的传感器数值的代码实际上就是图中的测量 ID,4097 和 4098。您也可以在测量 ID 列表中找到它们,并检查这些数值的单位是否与您的传感器匹配。

note

如果您在表格中找不到适合您的传感器类型。您可能需要使用传感器编号为 4165 到 4174 的自定义类型。传感器数值可能是无单位的。

步骤 4. 安装必要的库​

首先是 NTPClient 库,它可以使用 XIAO 网络功能来获取当前时间戳。

接下来是 ArduinoJson 库,它可以更容易地帮助我们解析 SenseCraft 返回给我们的内容。

步骤 5. 通过程序上传传感器数据​

以下是上传 SHT40 数据的程序。请注意,以下宏定义只有在修改为适合您的情况时才应使用。

const char* ssid = "YOUR-WIFI-NAME";
const char* password = "YOUR-WIFI-PASSWORD";
const char* apiKey = "YOUR-DEVICE-EUI&KEY-BASE64";
const char* deviceEUI = "YOUR-DEVICE-EUI";
const char* deviceKey = "YOUR-DEVICE-KEY";
const char* dataNum_1 = "4097"; // Air temperature
const char* dataNum_2 = "4098"; // Air humidity
const char* sensorType = "1001"; // Air temperature and humidity sensors

其中 ssid 和 password 指的是您的网络名称和密码。apiKey 指的是我们在步骤 2中获得的取证信息。deviceEUI 和 deviceKey 是您的设备 EUI 和密钥,这些也是在步骤 2中获得的。

#include <Arduino.h>
#include <SensirionI2CSht4x.h>
#include <Wire.h>
#include "WiFi.h"
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <WiFiUdp.h>
#include <NTPClient.h>

// Replace with your devive content
const char* ssid = "YOUR-WIFI-NAME";
const char* password = "YOUR-WIFI-PASSWORD";
const char* apiKey = "YOUR-DEVICE-EUI&KEY-BASE64";
const char* deviceEUI = "YOUR-DEVICE-EUI";
const char* deviceKey = "YOUR-DEVICE-KEY";
const char* dataNum_1 = "4097"; // Air temperature
const char* dataNum_2 = "4098"; // Air humidity
const char* sensorType = "1001"; // Air temperature and humidity sensors

const char* ntpServer = "pool.ntp.org";

WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, ntpServer);

char sensecapServer[] = "https://sensecap.seeed.cc/deviceapi/kit/message_uplink";

SensirionI2CSht4x sht4x;

void wifiConnect(){
WiFi.begin(ssid, password);
Serial.print("Connecting to ");
Serial.println(ssid);
while(WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.println("WiFi connected!");
Serial.println(WiFi.localIP());
}

void setup() {
Serial.begin(115200);
while (!Serial) {
delay(100);
}

Wire.begin();

uint16_t error;
char errorMessage[256];

sht4x.begin(Wire);

uint32_t serialNumber;
error = sht4x.serialNumber(serialNumber);
if (error) {
Serial.print("Error trying to execute serialNumber(): ");
errorToString(error, errorMessage, 256);
Serial.println(errorMessage);
} else {
Serial.print("Serial Number: ");
Serial.println(serialNumber);
}

wifiConnect();

timeClient.begin(); // Initialize the NTP client
timeClient.update(); // update timestamp
}

void loop() {
uint16_t error;
char errorMessage[256];

delay(1000);

float temperature;
float humidity;
error = sht4x.measureHighPrecision(temperature, humidity);
if (error) {
Serial.print("Error trying to execute measureHighPrecision(): ");
errorToString(error, errorMessage, 256);
Serial.println(errorMessage);
} else {
Serial.print("Temperature:");
Serial.print(temperature);
Serial.print("\t");
Serial.print("Humidity:");
Serial.println(humidity);
}

HTTPClient https;
if (https.begin(sensecapServer)) { // HTTPS
https.addHeader("Content-Type", "application/json");
String author = String("Device ") + apiKey;
https.addHeader("authorization", author);
String payload = String("{\"requestId\": \"aaaa-aaaa-aaaa-aaaa\", \"timestamp\": \"");
timeClient.update(); // update timestamp
uint64_t timestamp = timeClient.getEpochTime() * 1000ULL; // GET timestamp
payload += String(timestamp);
payload += String("\", \"intent\": \"event\", \"deviceEui\": \"");
payload += deviceEUI;
payload += String("\", \"deviceKey\": \"");
payload += deviceKey;
payload += String("\", \"events\": [{\"name\": \"measure-sensor\", \"value\": [{\"channel\": \"1\", \"measurements\": {\"");
payload += dataNum_1;
payload += String("\": \"");
payload += String(temperature);
payload += String("\", \"");
payload += dataNum_2;
payload += String("\": \"");
payload += String(humidity);
payload += String("\"}, \"measureTime\": \"");
payload += String(timestamp);
payload += String("\"}]}, {\"name\": \"update-channel-info\", \"value\": [{\"channel\": \"1\", \"sensorType\": \"");
payload += sensorType;
payload += String("\", \"status\": \"normal\"}], \"timestamp\": \"");
payload += String(timestamp);
payload += String("\"}]}");
Serial.println(payload);
int httpCode = https.POST(payload); // start connection and send HTTP header
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
String payload = https.getString();
Serial.println(payload);
}
else{
Serial.print("[HTTP] ERROR: ");
Serial.println(httpCode);
}
}
else{
Serial.println("[HTTPS] Unable to connect");
delay(1000);
}
delay(300000);
}

打开串口监视器,当你打开它时,程序将开始执行,当你收到如图所示的响应时,这意味着 SenseCraft 已成功接收到你的一次数据上传。

tip

SenseCraft 接收数据上传的最小时间间隔为五分钟。

SenseCraft 数据平台 HTTP API 介绍 -- 获取 AI 建议​

接下来,让我们学习如何使用下面的 SenseCraft 数据平台 API。使用我们随时间变化的传感器数据作为参考,从 AI 获取建议。

SenseCraft AI 的接口调用流程大致如下。

  • 通过接口 I 获取账户下支持在 AIGC 中使用的设备和测量值 ID。
  • 使用接口 I 获得的结果作为参数之一,调用接口 II 获取 AIGC 结果。
    • 由于 AIGC 生成时间可能较长,接口 I 在首次调用时将返回一个 resource_id,然后前端将使用 resource_id 轮询响应结果。当响应的代码为 11338 时,表示 AIGC 仍在推理过程中,需要使用 resource_id 再次调用接口 II,直到返回最终结果。
    • 接口 II 对同一账户在五分钟内最多有十次请求的流量限制。

HTTPS 服务器地址:

https://sensecap.seeed.cc/openapi

关于接口 I​

设备用于上报传感器数据的服务器路径为:/ai/view_suggestion_by_measurements,模式为 POST,以下请求参数可用且被允许。

名称描述备注
- lang选择语言1:中文,2:英文。默认中文。
- location位置位置,例如"深圳"。
- crop作物或动物要咨询的作物或动物,例如"苹果"。
- time_range设备数据查询时间1: 30天 2: 180天 3: 360天。默认30天
- measurements设备测量类型最多六个
-- dev_eui设备 EUI
-- channel_measurement
--- channel_index通道号此值目前固定为1。
--- measurement_ids测量值编号

以下是调用接口 I 的示例。

{
"lang": "2",
"crop": "apple",
"location": "Shenzhen",
"time_range": "1",
"measurements": [
{
"dev_eui": "2CF7F18215100010",
"channel_measurement": [
{
"channel_index": "1",
"measurement_ids": [
"4097",
"4098"
]
}
]
},
{
"dev_eui": "2CF7F1C043400103",
"channel_measurement": [
{
"channel_index": "1",
"measurement_ids": [
"4097"
]
}
]
}
]
}

关于接口 II​

接口 I 的结构和框架与接口 II 基本相同,唯一的区别是在末尾添加了一个额外的 resource_id。以下是可用和允许的请求参数。

名称描述备注
- lang选择语言1:中文,2:英文。默认中文。
- location位置位置,例如"深圳"。
- crop作物或动物要咨询的作物或动物,例如"苹果"。
- time_range设备数据查询时间1: 30天 2: 180天 3: 360天。默认30天
- measurements设备测量类型最多六个
-- dev_eui设备 EUI
-- channel_measurement
--- channel_index通道号此值目前固定为1。
--- measurement_ids测量值编号
- resource_id缓存凭证在已经提出问题并获得返回结果的情况下,携带此参数轮询后端,直到返回AI的结果。

以下是获取AI建议的示例。

{
"lang": "2",
"crop": "apple",
"location": "Shenzhen",
"time_range": "1",
"measurements": [
{
"dev_eui": "2CF7F18215100010",
"channel_measurement": [
{
"channel_index": "1",
"measurement_ids": [
"4097",
"4098"
]
}
]
},
{
"dev_eui": "2CF7F1C043400103",
"channel_measurement": [
{
"channel_index": "1",
"measurement_ids": [
"4097"
]
}
]
}
],
"resource_id": "openAi:ask:424326279298784:1691053698953"
}

XIAO ESP32C3 获取 SenseCraft AI 顾问​

步骤 6. 创建 API 访问权限​

如果您想调用 SenseCraft 的 AIGC 接口,那么您需要在 SenseCraft 中准备 API ID 和 API 访问密钥。在仪表板的左侧菜单栏中选择 Access API keys。然后点击顶部的 Create Access Key。

复制创建的 Access Key ID 和 Access API Key。请妥善保管它们,我们将在后续步骤中使用它们。

步骤 7. 编写和上传程序​

按照上述接口指南,我们可以编写一个程序,让 SenseCraft 使用我们 SHT40 的温湿度数据来为我们返回种植建议。

#include <Arduino.h>
#include <Wire.h>
#include "WiFi.h"
#include <HTTPClient.h>
#include <base64.h>
#include <ArduinoJson.h>

//#define DEBUG 1

// Replace with your devive content
const char* ssid = "YOUR-WIFI-NAME";
const char* password = "YOUR-WIFI-PASSWORD";
const char* username = "YOUR-API-ID";
const char* accesskey = "YOUR-ACCESS-API-KEY";
const char* deviceEUI = "YOUR-DEVICE-EUI";

const char* crop = "apple";
const char* location = "Shenzhen";
const char* timerange = "1";
const char* dataNum_1 = "4097"; //Air temperature
const char* dataNum_2 = "4098"; //Air humidity

char sensecapAIServer[] = "https://sensecap.seeed.cc/openapi/ai/view_suggestion_by_measurements";

void wifiConnect(){
WiFi.begin(ssid, password);
Serial.print("Connecting to ");
Serial.println(ssid);
while(WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.println("WiFi connected!");
Serial.println(WiFi.localIP());
}

void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
while (!Serial) {
delay(100);
}
wifiConnect();
}

String splicePayload(int mode, String resource_id = ""){
String payload = String("{\"lang\": \"2\", \"crop\": \""); // 1:Chinese 2:English
payload += crop;
payload += String("\", \"location\": \"");
payload += location;
payload += String("\", \"time_range\": \""); // 1:30 days, 2:180 days, 3:360 days
payload += timerange;
payload += String("\", \"measurements\": [{\"dev_eui\": \"");
payload += deviceEUI;
payload += String("\", \"channel_measurement\": [{\"channel_index\": \"1\", \"measurement_ids\": [\"");
payload += dataNum_1;
payload += String("\", \"");
payload += dataNum_2;
payload += String("\"]}]}");

//If you need values for other sensors
// payload += String(", {\"dev_eui\": \"");
// payload += deviceEUI_2;
// payload += String("\", \"channel_measurement\": [{\"channel_index\": \"1\", \"measurement_ids\": [\"");
// payload += dataNum_3;
// payload += String("\"]}]}");

if(mode == 1){
payload += String("]}");
}
else if(mode == 2){
// If a query code has been obtained. mode = 2
payload += String("], \"resource_id\": \"");
payload += resource_id;
payload += String("\"}");
}

Serial.println(payload);
return payload;
}

void loop() {
// put your main code here, to run repeatedly:
HTTPClient https;
if (https.begin(sensecapAIServer)) { // HTTPS
https.addHeader("Content-Type", "application/json");
String base64Credentials = base64::encode("YOUR-API-ID:YOUR-ACCESS-API-KEY");
https.addHeader("Authorization", "Basic " + base64Credentials);

String payload = splicePayload(1);

int httpCode = https.POST(payload); // start connection and send HTTP header
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
String response = https.getString();
#ifdef DEBUG
Serial.println(response);
#endif

// get resource_id
DynamicJsonDocument doc(1024);
deserializeJson(doc, response);
String resource_id = doc["data"]["resource_id"].as<String>();
Serial.println("resource_id: " + resource_id);

String payload = splicePayload(2, resource_id);

do{
delay(2000);
https.addHeader("Content-Type", "application/json");
String base64Credentials = base64::encode("YOUR-API-ID:YOUR-ACCESS-API-KEY");
https.addHeader("Authorization", "Basic " + base64Credentials);

int httpCode = https.POST(payload); // start connection and send HTTP header
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
String response = https.getString();
#ifdef DEBUG
Serial.println(response);
#endif

// Parsing JSON Responses
DynamicJsonDocument doc(1024);
deserializeJson(doc, response);
String code = doc["code"].as<String>();
Serial.println("status: " + code);
if(code == "0"){
DynamicJsonDocument doc(1024);
deserializeJson(doc, response);
String suggest = doc["data"].as<String>();
Serial.println("SenseCraft AI gives the following planting advice: ");
Serial.println(suggest);
break;
}
else if(code == "11396"){
Serial.println("Timeout. Please wait five minutes.");
break;
}
else Serial.println("Waiting for a reply...");
}
}while(1);
}
else{
Serial.print("[HTTP] ERROR: ");
Serial.println(httpCode);
}
}
else{
Serial.println("[HTTPS] Unable to connect");
delay(1000);
}
Serial.println("The next query will be in five minutes, so please do not query too often to avoid having your account restricted from use!");
delay(300000);
}

这里有一些需要注意的参数。在代码开头,宏定义 DEBUG 被注释掉了。如果取消注释这一行,那么程序可以执行并打印每次 SenseCraft 返回的消息。

在 DEBUG 下面是需要根据您的账户和设备进行更改的信息。例如,如果您不是在种植苹果且位置不在深圳,那么您需要更改为适合您情况的内容。

// Replace with your devive content
const char* ssid = "YOUR-WIFI-NAME";
const char* password = "YOUR-WIFI-PASSWORD";
const char* deviceEUI = "YOUR-DEVICE-EUI";

const char* crop = "apple";
const char* location = "Shenzhen";
const char* timerange = "1";
const char* dataNum_1 = "4097"; //Air temperature
const char* dataNum_2 = "4098"; //Air humidity

除此之外,代码中还有两行用于解析访问API。

String base64Credentials = base64::encode("YOUR-API-ID:YOUR-ACCESS-API-KEY");

例如,您在步骤 6中获得的 API ID 和 API Key 分别是 N0AV094KBPH1J9PX 和 5DFA8167D23C499C86F61BDEFB901D4995B896A267054D7DAD590BF67FB9A219。那么这行代码您应该更改为:

String base64Credentials = base64::encode("N0AV094KBPH1J9PX:5DFA8167D23C499C86F61BDEFB901D4995B896A267054D7DAD590BF67FB9A219");

上传此程序,您将看到上传消息并持续循环查看AI返回的答案结果,直到返回结果为止。

至此,恭喜您已经掌握了XIAO接入SenseCraft的所有知识和内容,我们非常欢迎您使用我们的XIAO和SenseCraft来发挥更多的创造力!

故障排除​

Q1: 为什么在获取AI答案后得到空结果?​

这可能是由于接口超时导致的,您可以等到下次查询发送后再检查结果。请注意,此结果只能获取一次,之后结果会立即被清除,无法再次查询。

技术支持与产品讨论​

感谢您选择我们的产品!我们在这里为您提供不同的支持,以确保您使用我们产品的体验尽可能顺畅。我们提供多种沟通渠道,以满足不同的偏好和需求。

Loading Comments...