目录
一、系统硬件设计系统硬件设计
1.1所需硬件
1.2接线表
二、程序模块设计
2.1温湿度检测模块
2.2气体检测模块(氨气检测)
2.3水位检测模块
2.3压力检测模块
2.4灰尘检测模块
2.5LCD展示模块
2.6Wi-Fi模块(重点)
三、完整代码及技术分析(主要为WiFi模块)
3.1完整代码:
3.2技术分析:(建议边使用串口调试助手边编写AT命令代码)
3.2.1本次实验使用非透传模式:
3.2.3透传模式:(个人认为这个比较丝滑)
四、总结
1、postData的问题:
2、WiFi模块和LCD模块
①Arduino UNO板*1
②温湿度传感器DHT11*1、粉尘传感器(GP2Y10)*1、气体传感器(MQ135)*1、水位传感器(Water Sensor)*1、压力传感器(HX7111)*1
③Wi-Fi模块(esp8266)*1
④LCD显示屏(ST7735)*1
⑤电容*1、电阻*1、面包板*3
⑥杜邦线若干
1.2接线表温湿度传感器
Arduino UNO
正极
3.3V
负极
GND
S(数据口)
4(数字接口)
气体传感器(MQ135)
Arduino UNO
正极
5V
负极
GND
A0
A0(模拟接口)
水位传感器
Arduino UNO
正极
5V
负极
GND
S
A2
压力传感器
Arduino UNO
VCC
5V
GND
GND
DT
7(数字接口)
SCK
6(数字接口)
灰尘传感器(接线从左往右)
Arduino UNO
V-LED
5V
LED-GND
GND
LED
5(数字接口)
S-GND
GND
VO
A1(模拟接口)
VCC
5V
显示屏(ST7735)
Arduino UNO
VDD
5V
GND
GND
SCK
13(数字接口)
SDA
11(数字接口)
CS
10(数字接口)
DC
9(数字接口)
RST
8(数字接口)
float temperature = 0;
float humidity = 0;
temperature = dht.readTemperature();
humidity = dht.readHumidity();
bool dhtStatus = !(isnan(temperature) || isnan(humidity));
sensorStatus += dhtStatus ? "1" : "0";
if(!dhtStatus)
{
temperature = 0;
humidity = 0;
}
分析:使用库文件(DHT.H),创建dht对象进行获取温湿度,最后判断温湿度是否能读取,根据这个设置好温湿度传感器的运行状态。
2.2气体检测模块(氨气检测)int gasSensorValue = analogRead(gasSensorPin);
float resistance = (1023.0 - gasSensorValue) * 10000 / gasSensorValue;
float compensatedResistance = compensateResistance(resistance, temperature, humidity);
float ammoniaConcentration = convertResistanceToConcentration(compensatedResistance);
bool gasStatus = (gasSensorValue >= 0 && gasSensorValue <= 80);
sensorStatus += gasStatus ? "1" : "0";
if(!gasStatus)
{
ammoniaConcentration = 0;
}
float compensateResistance(float resistance, float temperature, float humidity) {
float compensatedResistance = resistance * (1 + temperature / 100.0) * (1 - humidity / 200.0);
return compensatedResistance;
}
float convertResistanceToConcentration(float compensatedResistance) {
float concentration = 100 / (compensatedResistance / 20000.0 + 1);
return concentration;
}
分析:不需要使用额外的库,直接获取模拟引脚的值,再根据当前的温湿度来补偿电阻值,根据电阻值转换为氨气的浓度。根据气体传感器未正常运行的模拟值来设置传感器的运行状态。
2.3水位检测模块int waterSensorValue = analogRead(waterSensorPin);
int waterLevel = map(waterSensorValue, 0,670, 0, 100);
bool waterStatus = (waterSensorValue >= 0 && waterSensorValue <= 100)||(waterSensorValue >= 110 && waterSensorValue <= 1023);
sensorStatus += waterStatus ? "1" : "0";
if(!waterStatus)
{
waterLevel = 0;
}
分析:不需要使用额外的库,根据模拟引脚的值获取值,并且根据实际情况将值映射为0-100的值。再根据模拟数字来判断传感器的运行状态。
2.3压力检测模块HX711 scale;
float tareWeight = 0;
float calibration_factor = 405.0;
scale.begin(HX711_DT, HX711_SCK);
tareWeight = scale.read_average(20);
float actualWeight = 658.0;
float detectedWeight = 645.0;
calibration_factor = calibration_factor * (detectedWeight / actualWeight);
float rawValue = scale.read_average(10);
float netWeight = (rawValue - tareWeight) / calibration_factor;
bool scaleStatus = (rawValue != 0);
sensorStatus += scaleStatus ? "1" : "0";
if(!scaleStatus)
{
netWeight = 0;
}
分析:先获取新的校准因子,然后调用HX711 库来获取当前食物的重量,通过读取的状态来获取传感器运行状态。
2.4灰尘检测模块digitalWrite(ledPower,LOW);
delayMicroseconds(samplingTime);
voMeasured = analogRead(measurePin);
delayMicroseconds(deltaTime);
digitalWrite(ledPower,HIGH);
delayMicroseconds(sleepTime);
calcVoltage = voMeasured * (5.0/1024.0);
pmTwoFiveDensity =170 * calcVoltage;
bool pmStatus = (voMeasured >= 0 && voMeasured <= 90)||(voMeasured >= 120 && voMeasured <= 1023);
sensorStatus += pmStatus ? "1" : "0";
if(!pmStatus)
{
pmTwoFiveDensity = 0;
}
分析:使用夏普GP2Y10测量猫笼的环境清洁度的值,然后根据断开后的传感器的模拟值来设置传感器的运行状态。
2.5LCD展示模块stup:
tft.initR(INITR_GREENTAB);
tft.fillScreen(ST7735_BLACK);
loop:
tft.fillScreen(ST7735_BLACK);
tft.setTextColor(ST7735_WHITE);
tft.setCursor(2, 10);
tft.print("TempStatus: ");
tft.print(dhtStatus ? "OK" : "NO");
tft.setCursor(2, 20);
tft.print("Temp: ");
tft.print(temperature);
tft.print(" C ");
tft.setCursor(2, 30);
tft.print("HumidityStatus: ");
tft.print(dhtStatus ? "OK" : "NO");
tft.setCursor(2, 40);
tft.print("Humidity: ");
tft.print(humidity);
tft.print(" % ");
tft.setCursor(2, 50);
tft.print("pmStatus: ");
tft.print(pmStatus ? "OK" : "NO");
tft.setCursor(2, 60);
tft.print("PM2.5: ");
tft.print(pmTwoFiveDensity);
tft.print(" ug/m3 ");
tft.setCursor(2, 70);
tft.print("WaterStatus: ");
tft.print(waterStatus ? "OK" : "NO");
tft.setCursor(2, 80);
tft.print("Water Level: ");
tft.print(waterLevel);
tft.print(" % ");
tft.setCursor(2, 90);
tft.print("AmmoniaStatus: ");
tft.print(gasStatus ? "OK" : "NO");
tft.setCursor(2, 100);
tft.print("Ammonia: ");
tft.print(ammoniaConcentration);
tft.print(" ppm ");
tft.setCursor(2, 110);
tft.print("WeightStatus: ");
tft.print(scaleStatus ? "OK" : "NO");
tft.setCursor(2, 120);
tft.print("Weight: ");
tft.print(netWeight, 1);
tft.print(" g ");
tft.setCursor(2, 130);
tft.print("Status: ");
tft.print(sensorStatus);
delay(2000);
分析:先初始化 ST7735 显示屏,设置背景为黑色,然后在loop函数里面设置好显示位置及大小将传感器状态及数据显示出来。
2.6Wi-Fi模块(重点)可以先把esp8266插到usb的调试器上使用串口调试助手来检测
void connectToWiFi()
{
bool wifiConnected = false;
Serial.println("AT+CWJAP="" + ssid + "","" + password + """ + "rn");
espSerial.println("AT+CWMODE=1");
while (!wifiConnected)
{
espSerial.println("AT+CWJAP="" + String(ssid) + "","" + String(password) + """ + "rn");
delay(2000);
if (espSerial.find("OK")) {
Serial.println("Connected to WiFi.");
wifiConnected = true;
break;
} else {
Serial.println("Connection failed. Retrying...");
delay(2000);
}
}
}
void sendHttpPostRequest(const String& url, const String& postData) {
String host = "172.20.10.3";
int port = 8080;
espSerial.println("AT+CIPSTART="TCP","" + host + ""," + String(port));
delay(2000);
if (espSerial.find("CONNECT")||espSerial.find("ALREADY CONNECTED")) {
Serial.println("Connect Succesfullyn");
String cmd = "AT+CIPSEND=";
cmd += String(postData.length() + 287 );
espSerial.println(cmd);
if (espSerial.find(">")) {
Serial.println("begin to send!!!");
espSerial.println("POST /sensor/addRecord HTTP/1.1");
espSerial.println("Host: "+ host +":"+port);
espSerial.println("Content-Type: application/json");
espSerial.println("Content-Length: " + String(postData.length()));
espSerial.println("Authorization: " + token);
espSerial.println();
espSerial.print(postData);
delay(1000);
Serial.println("----------------------------------------------");
Serial.println("POST /sensor/addRecord HTTP/1.1");
Serial.println("Host: "+ host +":"+port);
Serial.println("Content-Type: application/json");
Serial.println("Content-Length: " + String(postData.length()));
Serial.println("Authorization: " + token);
Serial.println();
Serial.println(postData);
Serial.println("----------------------------------------------");
}
else {
Serial.println("Failed to initiate data transmission.");
}
} else {
Serial.println("Failed to connect to server.");
}
}
分析:在setup中先连接WiFi,再到loop中进行TCP服务数据传输。
连接WiFi:AT+CWJAP="WiFi名","密码"
设置好AT模式:AT+CWMODE=1
进行TCP服务连接:AT+CIPSTART="TCP","172.20.10.3",8080
进入发送状态:AT+CIPSEND=373(总长度=报文头长度+传输数据长度postdata)
这边获取长度可能会很麻烦出现问题,有两个解决办法:①就是自己再编写一个程序来检测长度②使用透传模式(下面会有实例代码)
我的检测长度代码:(建议编写后串口输出格式来检查,并且同时用串口调试助手来检测是否成功)
注意:数据报文里面的长度是指数据的长度记得相对应,而非透传的AT的长度是头加数据的长度。
const String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGFpbXMiOnsiaWQiOjEsInVzZXJuYW1lIjoieXVoaWRva2kifSwiZXhwIjoxNzIwMDU0NzY4fQ.do1vL_jNUXuACqkyf9Kxcgd_PO7wcyt3eYagpugTNuo"; // 替换成你的实际token
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
}
void loop() {
// Empty{"r":"2", "s":"11111", "t":"24.90", "h":"72", "p":"989","a":"9.24", "w":"88", "f":"1"}//86,405
// String postData0 = "{"r":"2", "s":"11111", "t":"24.90", "h":"72", "p":"989","a":"9.24", "w":"88", "f":"1"}";
String postData0 = "{"humidity":"70","temperature":"23"}";
// String postData = "POST /sensor/addRecord HTTP/1.1rn" + "Host: 172.20.10.3:8080rn" + "Content-Type: application/jsonrn" + "Content-Length: 89rn" + "Authorization: ";
String postData = "POST /sensordata HTTP/1.1rn"
"Host: 172.21.10.65:8080rn"
"Content-Type: application/jsonrn"
"Content-Length: 86rn";
// 计算 JSON 数据的长度
int contentLength = postData.length();
int contentLength1 = postData0.length();
// 打印计算结果
Serial.println(postData);
Serial.println(postData0);
Serial.print("JSON 数据的长度为: ");
Serial.println(contentLength);
Serial.println(contentLength1);
delay(10000);
}
发送数据报文:(根据你设定的数据报文来编写,因为为了服务器的安全可能会要求客户端需要key来请求响应,本实验就是token这个参数,后面需要的可以根据自己来定报文格式)(下面的换行必需要有)
POST /sensordata HTTP/1.1
Host: 172.21.10.65:8080
Content-Type: application/json
Content-Length: 36
{"temperature":"23","humidity":"70"}
已经注释掉发送的postData的调试代码及LCD显示代码。
#include <SoftwareSerial.h>
#include <DHT.h>
#include "HX711.h"
#define DHTPIN 4
#define DHTTYPE DHT11
#define POST_TIME 3000
SoftwareSerial espSerial(2, 3);
DHT dht(DHTPIN, DHTTYPE);
const String ssid = "";
const String password = "";
const String host = "172.20.10.3";
const int port = 8080;
const String url = "/sensor/addRecord";
const String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGFpbXMiOnsiaWQiOjEsInVzZXJuYW1lIjoieXVoaWRva2kifSwiZXhwIjoxNzIwMzE0MDYzfQ.vm_0TNIRJOIhPifWcv1Jz_3pEJYzAmORUbGoR5K7wCY";
const String relatedCage = "2";
int samplingTime = 280;
int deltaTime = 40;
int sleepTime = 9680;
float voMeasured = 0;
float calcVoltage =0;
float pmTwoFiveDensity=0;
float pmTenDensity=0;
const int gasSensorPin = A0;
const int measurePin = A1;
const int ledPower = 5;
const int waterSensorPin = A2;
const int HX711_SCK = 6;
const int HX711_DT = 7;
const int buzzerPin = 8;
HX711 scale;
float tareWeight = 0;
float calibration_factor = 405.0;
void setup() {
Serial.begin(115200);
espSerial.begin(115200);
dht.begin();
scale.begin(HX711_DT, HX711_SCK);
tareWeight = scale.read_average(20);
float actualWeight = 658.0;
float detectedWeight = 645.0;
calibration_factor = calibration_factor * (detectedWeight / actualWeight);
connectToWiFi();
}
void loop() {
String sensorStatus = "";
float temperature = 0;
float humidity = 0;
temperature = dht.readTemperature();
humidity = dht.readHumidity();
bool dhtStatus = !(isnan(temperature) || isnan(humidity));
sensorStatus += dhtStatus ? "1" : "0";
if(!dhtStatus)
{
temperature = 0;
humidity = 0;
}
digitalWrite(ledPower,LOW);
delayMicroseconds(samplingTime);
voMeasured = analogRead(measurePin);
delayMicroseconds(deltaTime);
digitalWrite(ledPower,HIGH);
delayMicroseconds(sleepTime);
calcVoltage = voMeasured * (5.0/1024.0);
pmTwoFiveDensity =170 * calcVoltage;
bool pmStatus = (voMeasured >= 0 && voMeasured <= 65)||(voMeasured >= 120 && voMeasured <= 1023);
sensorStatus += pmStatus ? "1" : "0";
if(!pmStatus)
{
pmTwoFiveDensity = 0;
}
int gasSensorValue = analogRead(gasSensorPin);
float resistance = (1023.0 - gasSensorValue) * 10000 / gasSensorValue;
float compensatedResistance = compensateResistance(resistance, temperature, humidity);
float ammoniaConcentration = convertResistanceToConcentration(compensatedResistance);
bool gasStatus = (gasSensorValue >= 30 && gasSensorValue <= 80);
sensorStatus += gasStatus ? "1" : "0";
if(!gasStatus)
{
ammoniaConcentration = 0;
}
int waterSensorValue = analogRead(waterSensorPin);
int waterLevel = map(waterSensorValue, 47,670, 0, 100);
bool waterStatus = (waterSensorValue >= 0 && waterSensorValue <= 60)||(waterSensorValue >= 75 && waterSensorValue <= 1023);
sensorStatus += waterStatus ? "1" : "0";
if(!waterStatus)
{
waterLevel = 0;
}
float rawValue = scale.read_average(10);
float netWeight = (rawValue - tareWeight) / calibration_factor;
bool scaleStatus = (rawValue != 0);
sensorStatus += scaleStatus ? "1" : "0";
if(!scaleStatus)
{
netWeight = 0;
}
String postData = "{"r":"2", "s":"" + String(sensorStatus) + "", "t":"" + String(temperature) + "", "h":"" + String(humidity) + "", "p":"" + String(pmTwoFiveDensity) + "","a":"" + String(ammoniaConcentration) + "", "w":"" + String(waterLevel) + "", "f":"" + String(netWeight) + ""}";
sendHttpPostRequest("http://172.20.10.3:8080/sensor/addRecord", postData);
delay(POST_TIME);
}
void connectToWiFi()
{
bool wifiConnected = false;
Serial.println("AT+CWJAP="" + ssid + "","" + password + """ + "rn");
espSerial.println("AT+CWMODE=1");
while (!wifiConnected)
{
espSerial.println("AT+CWJAP="" + String(ssid) + "","" + String(password) + """ + "rn");
delay(2000);
if (espSerial.find("OK")) {
Serial.println("Connected to WiFi.");
wifiConnected = true;
break;
} else {
Serial.println("Connection failed. Retrying...");
delay(2000);
}
}
}
void sendHttpPostRequest(const String& url, const String& postData) {
String host = "172.20.10.3";
int port = 8080;
espSerial.println("AT+CIPSTART="TCP","" + host + ""," + String(port));
delay(2000);
if (espSerial.find("CONNECT")||espSerial.find("ALREADY CONNECTED")) {
Serial.println("Connect Succesfullyn");
String cmd = "AT+CIPSEND=";
cmd += String(postData.length() + 287 );
espSerial.println(cmd);
if (espSerial.find(">")) {
Serial.println("begin to send!!!");
espSerial.println("POST /sensor/addRecord HTTP/1.1");
espSerial.println("Host: "+ host +":"+port);
espSerial.println("Content-Type: application/json");
espSerial.println("Content-Length: " + String(postData.length()));
espSerial.println("Authorization: " + token);
espSerial.println();
espSerial.print(postData);
Serial.println("----------------------------------------------");
Serial.println("POST /sensor/addRecord HTTP/1.1");
Serial.println("Host: "+ host +":"+port);
Serial.println("Content-Type: application/json");
Serial.println("Content-Length: " + String(postData.length()));
Serial.println("Authorization: " + token);
Serial.println();
Serial.println(postData);
Serial.println("----------------------------------------------");
}
else {
Serial.println("Failed to initiate data transmission.");
}
} else {
Serial.println("Failed to connect to server.");
}
}
bool checkResponseForSubstring(const String& targetSubstring) {
bool substringFound = false;
String response = "";
while (espSerial.available()) {
char c = espSerial.read();
response += c;
Serial.print(c);
}
if (response.indexOf(targetSubstring) != -1) {
substringFound = true;
}
Serial.println("Full Response:");
Serial.println(response);
return substringFound;
}
float compensateResistance(float resistance, float temperature, float humidity) {
float compensatedResistance = resistance * (1 + temperature / 100.0) * (1 - humidity / 200.0);
return compensatedResistance;
}
float convertResistanceToConcentration(float compensatedResistance) {
float concentration = 100 / (compensatedResistance / 20000.0 + 1);
return concentration;
}
3.2技术分析:(建议边使用串口调试助手边编写AT命令代码) 3.2.1本次实验使用非透传模式:①先使用AT命令连接WiFi:“666”为wifi名,“111”为密码
AT+CWJAP="666","111"
②设置模块模式:
AT+CWMODE=1
③连接TCP服务:"192.168.148.194"为服务器IP,8080为使用的端口
AT+CIPSTART="TCP","192.168.148.194",8080
④开始发送数据:373为报文头长度加数据包的长度
AT+CIPSEND=373
⑤发送数据:格式要如下:/sensor/addRecord这个为你的api接口,
Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGFpbXMiOnsiaWQiOjEsInVzZXJuYW1lIjoieXVoaWRva2kifSwiZXhwIjoxNzIwMzE0MDYzfQ.vm_0TNIRJOIhPifWcv1Jz_3pEJYzAmORUbGoR5K7wCY,这个字符串为为了安全的用户key,默认为没有这个需要根据后台来获取设计(这个可以不设计)
记得还有换行符不要漏了。
{"r":"2", "s":"11111", "t":"24.90", "h":"72", "p":"989","a":"9.24", "w":"88", "f":"1"}接下来就是数据格式按照这个来。
POST /sensor/addRecord HTTP/1.1
Host: 172.20.10.3:8080
Content-Type: application/json
Content-Length: 86
Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGFpbXMiOnsiaWQiOjEsInVzZXJuYW1lIjoieXVoaWRva2kifSwiZXhwIjoxNzIwMzE0MDYzfQ.vm_0TNIRJOIhPifWcv1Jz_3pEJYzAmORUbGoR5K7wCY
{"r":"2", "s":"11111", "t":"24.90", "h":"72", "p":"989","a":"9.24", "w":"88", "f":"1"}
3.2.3透传模式:(个人认为这个比较方便简单)①实例代码,传输温湿度(根据自己的情况来改写代码的参数)
#include <SoftwareSerial.h>
#include <DHT11.h>
#define POST_TIME 3000
SoftwareSerial espSerial(2, 3);
DHT11 dht11(7);
const String ssid = "";
const String password = "";
const String host = "172.19.76.71";
const int port = 8080;
const String url = "http://172.19.76.71:8080/sensordata";
void setup()
{
Serial.begin(115200);
espSerial.begin(115200);
connectToServer();
}
void loop()
{
int temperature = 0;
int humidity = 0;
int result = dht11.readTemperatureHumidity(temperature, humidity);
String postData = "{ "humidity":"" + String(humidity) + "","temperature":"" + String(temperature) + ""}";
sendHttpPostRequest(postData);
delay(POST_TIME);
}
void connectToServer()
{
espSerial.println("AT+CWQAP");
bool wifiConnected = false;
bool flag = false;
espSerial.println("AT+CWMODE=1");
while (!wifiConnected)
{
espSerial.println("AT+CWJAP="" + String(ssid) + "","" + String(password) + """ + "rn");
delay(2000);
if (espSerial.find("OK")) {
Serial.println("Connected to WiFi.");
wifiConnected = true;
break;
} else {
Serial.println("Connection failed. Retrying...");
delay(2000);
}
}
espSerial.println("AT+CIPMODE=1");
espSerial.println("AT+CIPMUX=0");
while(!flag)
{
espSerial.println("AT+CIPSTART="TCP","" + host + ""," + String(port));
delay(2000);
if(espSerial.find("OK")){
Serial.println("Connected to Server.");
flag=true;
break;
}
else{
Serial.println("Connected to Server failed.");
delay(2000);
}
}
espSerial.println("AT+CIPSEND");
}
void sendHttpPostRequest(const String& postData) {
espSerial.println("POST /sensordata HTTP/1.1");
espSerial.println("Host: "+ host + ":" + port);
espSerial.println("Content-Type: application/json");
espSerial.println("Content-Length: " + String(postData.length()));
espSerial.println();
espSerial.print(postData);
Serial.println("发送数据:"+ postData);
}
②原理:
按照以下的AT命令来输出就行了(可以先用串口调试助手来实验一下)
连接WiFi:AT+CWJAP="wifi名","密码"
AT+CWMODE=1
AT+CIPMODE=1
AT+CIPMUX=0
AT+CIPSTART="TCP","172.19.76.71",8080//记得修改服务器IP和端口
AT+CIPSEND//开始发送数据不需要写长度
数据报文格式:
POST /sensordata HTTP/1.1
Host: 172.19.76.71:8080
Content-Type: application/json
Content-Length: 35
{"humidity":60,"temperature":25}
当postData传输的数据长度较短时,例如温湿度,传输过去是没有任何问题的。但本次实验是传输了8个数据,但postData会丢失然后将其串口输出为空值,后来将其分段输出串口可以显示两端字符串,但使用esp8266输出数据发现两个字符串中有一个会丢失。后来将总体变短如本实验变量均为一个字母代表。(个人认为可能是长度太长及硬件存储问题)。
2、WiFi模块和LCD模块本次实验未能将WiFi和LCD一同使用,因为接上LCD模块后发现postData会出现为空,所以注释掉LCD模块,但不使用WiFi模块,LCD可以正常显示,(个人认为为资源抢夺问题)如下图:
总的来说,使用wifi模块离不开串口调试器的使用。
相关知识
基于arduino和机智云平台的智能宠物屋设计
基于arduino和机智云平台的智能宠物屋研究与实现
基于Arduino设计的简易宠物喂食机 DF创客社区
温度传感器 – Arduino 实验室
基于机智云宠物管理系统总体设计
【雕爷学编程】Arduino智能家居之智能宠物管理助手
基于STM32的猫咪健康管理监护系统设计.docx
用Arduino Uno制作一个智能的自动宠物喂食器
基于单片机的智慧宠物窝系统设计(论文+源码)
基于微信平台的体重健康管理系统设计
网址: 基于Arduino的宠物管理系统传感器设计(仅硬件部分) https://m.mcbbbk.com/newsview536285.html
上一篇: 宠物笼设计素材 |
下一篇: 凡思空间多功能单层猫狗铁笼子铁丝 |