Using XIAO ESP32C3 to connect to SenseCAP AI for planting advice
During this time, Seeed Studio's SenseCAP platform developed and released new AI features. Currently the main features of SenseCAP AI are focused on providing constructive planting advice to growers, and will be updated with richer AI features in the near future!
This tutorial, then, will bridge the XIAO ESP32 series with the SenseCAP platform, detailing how to use the XIAO and Grove series sensors to upload data to SenseCAP and get constructive suggestions from the AI based on these sensor values.
Getting Started
Hardware Preparation
The three pieces of hardware that will be used in this tutorial are the XIAO ESP32C3, the Grove Base for XIAO, and the Grove SHT40 temperature and humidity sensor. For the convenience of wiring, we use the XIAO expansion board, which you can purchase according to your actual needs.
Seeed Studio XIAO ESP32C3 | Grove Base for XIAO | Grove - Temperature & Humidity Sensor(SHT40) |
---|---|---|
Software Preparation
If this is your first time using the XIAO ESP32C3, then you can need to read this Wiki first to learn to configure a good development environment in Arduino.
Also, this tutorial is compatible with XIAO ESP32S3, you can also use XIAO ESP32S3 to complete the content of this tutorial.
Since the SHT40 sensor is used, you also need to add the following two libraries well in Arduino to ensure that the program can run smoothly.
XIAO ESP32C3 Getting Temperature and Humidity Data
As shown in the figure below, please connect the Grove SHT40 temperature and humidity sensor to the IIC interface of the XIAO.
Then upload the following program for the XIAO ESP32C3 to drive the SHT40 sensor to work and start getting the temperature and humidity values in the air.
#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);
}
}
Open the serial monitor of Arduino IDE and select the baud rate as 115200 and observe the result.
Introduction to SenseCAP HTTPS API -- Upload Sensor Data
Now that we know how to get data from the SHT40 sensor, let's start by learning the following API call rules for the SenseCAP platform. You can read about using the SenseCAP API by clicking the button below to jump directly to the SenseCAP Documentation Center.
The basic principle of SenseCAP to receive sensor data is to use EUI, Key as the authentication information and report the device data by POST.
HTTPS Server Address:
https://sensecap.seeed.cc/deviceapi
About Header
In POST, you need to add the authentication information in the Header, which is the base64 encrypted data of the device's EUI and Key in the following basic format.
authorization = Device base64(EUI:Key)
About Interface
The server path to be used by the device to report sensor data is: /kit/message_uplink
, the mode is POST, and the following request parameters are available and allowed.
Name | Type | Description |
---|---|---|
- requestId | string | The uuidv4 is generated on the device side each time data is reported, ensuring that the value is different for each message. |
- timestamp | string | Millisecond timestamp when the message was sent. |
- intent | string | Currently fixed to "event". |
- deviceEui | string | Device EUI. |
- deviceKey | string | Device Key. |
- events | [object] | An array of events where data collection and device status is reported. |
-- name | string | Event Name. |
-- value | [object] | Event Value. |
-- timestamp | string | Millisecond timestamp at the time of data collection. |
The following is an example of sending sensor upload data.
{
"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"
}
]
}
Upload temperature and humidity data to SenseCAP
Once we understand the above rules, we can start writing the HTTPS program to upload the temperature and humidity data of our SHT40 for SenseCAP.
Step 1. Register and login to SenseCAP
You can click on the link below to go directly to the SenseCAP International site. If this is your first time using SenseCAP's services, you may need to register for an account.
Logging in to SenseCAP takes you to the console screen. We need to add a kit of our own, please click DevelopKit on the left menu bar.
Then click Create DevelopKit in the upper left.
Then just select the MIG Develop Kit and click the Confirm button.
Next, you'll be able to see the device you created in the dashboard, usually the first one. Clicking on the "Connect" button for that device will display the EUI and KEY information for that device. Please save them, we will use them in the next steps.
Step 2. Obtaining Forensic Information
SenseCAP's interface for authentication information requires Base64-based EUI:KEY encryption.
For example, your EUI is 2CF7F11003900000
and Key is 06C42483D7155E7006C42483D7155E70
. then you can get the Base64 encrypted forensic information by the following commands in the terminal.
echo -n 2CF7F11003900000:06C42483D7155E7006C42483D7155E70 |base64
>>> MkNGN0YxMTAwMzkwMDAwMDowNkM0MjQ4M0Q3MTU1RTcwMDZDNDI0ODNENzE1NUU3MA==
Please keep the authentication information, we will use it as the apiKey in the program later.
Step 3. Obtaining the number of the sensor type
Included in the upload is the number of the sensor type and sensor name that we are reporting. This is so that SenseCAP knows what sensor we are uploading data from and what units the data is in.
For this section, please refer to the numbered cross-reference table of sensors and values provided in the SenseCAP Documentation Center.
How to use these two documents, we will use the SHT40 sensor used in this article as an example, to give you an introduction to explain. The SHT40 sensor is a sensor that can measure both temperature and humidity data. So he has a code for the sensor type and two codes for the (temperature, humidity) values.
The code for a sensor type we need to look up in List of Sensor Types. We found a temperature and humidity sensor with the code 1001.
The codes for the sensor values we're talking about are actually the Measurement IDs in the diagram, 4097 and 4098. you can also find them in the List of Measurement IDs and check to see if the units of these values match your sensor.
If you can't find the right type of sensor for you in the table. You may need to use a custom type with a sensor number of 4165 to 4174. the sensor value may be unitless.
Step 4. Install the necessary libraries
First is the NTPClient library, which can use XIAO networking to get the current timestamp.
Next is the ArduinoJson library, which makes it easier to help us parse what SenseCAP returns to us.
Step 5. Uploading sensor data through the program
The following is the procedure for uploading SHT40 data. Please note that the following macro definitions should only be used if they are modified to suit your situation.
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
Where ssid and password refer to your network name and password. apiKey refers to the forensic information we obtained in Step 2. deviceEUI and deviceKey are your device EUI and Key, which were also obtained in Step 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);
}
Turn on the serial monitor, when you turn it on, the program will start to execute, when you receive the response as shown in the figure, it means that SenseCAP has successfully received one of your data uploads.
The minimum time interval for SenseCAP to receive data uploads is five minutes.
Introduction to SenseCAP HTTPS API -- Getting AI Advice
Next, let's learn how to use the SenseCAP AI API below. Use our sensor data over time as a reference to get suggestions from the AI.
The interface call process for SenseCAP AI is roughly as follows.
- Obtain the device and measurement value IDs under the account that are supported for use in AIGC via the interface I.
- Using the result obtained by interface I as one of the parameters, interface II is called to obtain the AIGC result.
- Since the AIGC generation time may be long, the interface I will return a resource_id when it is called for the first time, and then the front-end will use the resource_id to poll for the result of the response. When the code of the response is 11338, it means that the AIGC is still in the process of inference, and the interface II needs to be called again with the resource_id until the final result is returned.
- Interface II has a flow limit of up to ten requests within five minutes for the same account.
HTTPS Server Address:
https://sensecap.seeed.cc/openapi
About Interface I
The server path to be used by the device to report sensor data is: /ai/view_suggestion_by_measurements
, the mode is POST, and the following request parameters are available and allowed.
Name | Description | Note |
---|---|---|
- lang | Select language | 1:Chinese, 2:English. Default Chinese. |
- location | Location | Location, e.g. "Shenzhen". |
- crop | Crop or animal | Crop or animal to be consulted, e.g. "apple". |
- time_range | Device data inquiry time | 1: 30 days 2: 180 days 3: 360 days. Default 30 days |
- measurements | Device measurement types | Up to six |
-- dev_eui | Device EUI | |
-- channel_measurement | ||
--- channel_index | Channel number | This value is currently fixed at 1. |
--- measurement_ids | Measurement value number |
The following is an example of calling the Interface 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"
]
}
]
}
]
}
About Interface II
The structure and framework of Interface I is largely the same as Interface II, with the only difference being the addition of an extra resource_id at the end. The following request parameters are available and allowed.
Name | Description | Note |
---|---|---|
- lang | Select language | 1:Chinese, 2:English. Default Chinese. |
- location | Location | Location, e.g. "Shenzhen". |
- crop | Crop or animal | Crop or animal to be consulted, e.g. "apple". |
- time_range | Device data inquiry time | 1: 30 days 2: 180 days 3: 360 days. Default 30 days |
- measurements | Device measurement types | Up to six |
-- dev_eui | Device EUI | |
-- channel_measurement | ||
--- channel_index | Channel number | This value is currently fixed at 1. |
--- measurement_ids | Measurement value number | |
- resource_id | Cached Credentials | In the case where a question has been asked and the returned result is obtained, carry this parameter to poll the backend until the result of the AI is returned. |
The following is an example of getting AI advice.
{
"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 Getting SenseCAP AI Answer
Step 6. Create API Access
If you want to call the AIGC interface of SenseCAP, then you need to prepare the API ID and API Access Key in SenseCAP. Select Access API keys in the left menu bar of the dashboard. Then click Create Access Key at the top.
Copy the created Access Key ID and Access API Key. Please keep them safe and we will use them in the following steps.
Step 7. Write and upload programs
Following the interface guidelines above, we can then write a program that allows SenseCAP to use the temperature and humidity data from our SHT40 to return planting recommendations to us.
#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("SenseCAP 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);
}
Here, there are some parameters to keep an eye on. At the beginning of the code, a macro definition DEBUG
is commented out. if this line is uncommented, then the program can be executed to print the message returned each time SenseCAP returns.
Below DEBUG
is the information that needs to be changed depending on your account and device. For example, if you are not growing apples and the location is not Shenzhen, then you need to change it to suit your situation.
// 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
In addition to this, there are two lines in the code that parse the Access API.
String base64Credentials = base64::encode("YOUR-API-ID:YOUR-ACCESS-API-KEY");
For example, the API ID and API Key you obtained in step 6 are N0AV094KBPH1J9PX
and 5DFA8167D23C499C86F61BDEFB901D4995B896A267054D7DAD590BF67FB9A219
, respectively. Then this line of code you should change to:
String base64Credentials = base64::encode("N0AV094KBPH1J9PX:5DFA8167D23C499C86F61BDEFB901D4995B896A267054D7DAD590BF67FB9A219");
Upload this program and you will see the message upload and keep looping through the results of the answers returned by the AI until the results are returned.
At this point, congratulations you have mastered all the knowledge and content of XIAO access SenseCAP, we very much welcome you to use our XIAO and SenseCAP to utilize more of your creativity!
Troubleshooting
Q1: Why am I getting null results after getting AI answers?
This may be due to an interface timeout, you can wait until the next query is sent before checking the results. Please note that this result can only be fetched once, after that the result is immediately erased and can no longer be queried.
Tech Support & Product Discussion
Thank you for choosing our products! We are here to provide you with different support to ensure that your experience with our products is as smooth as possible. We offer several communication channels to cater to different preferences and needs.