從零開始的DIY智慧家居 - 基於 ESP32 的智慧澆水器
前言
上次 土壤溼度感測器 完成之後,就立下一個 flag 要搭建一個智慧澆水的智慧場景,現在終於有時間填坑了!(o゚▽゚)o
智慧澆水場景的核心裝置有三個:
檢測土壤狀態的:土壤溼度感測器 通過這個感測器來獲取土壤資訊,作為是否澆水的依據。
智慧澆水器:執行裝置,通過 Spirit 1 控制。
Spirit 1
這次就來製作智慧澆水的智慧場景的核心: 智慧澆水器,我準備買一個便宜的傻不拉幾的澆水器自己改造一下,想辦法給他連上腦子。
主要互動流程如下圖:
(σ゚∀゚)σ..:*☆哎喲不錯哦,是不是很厲害啊!
硬體選擇
萬年不變的 安信可的 ESP32S ,別問,問就是便宜才 24元。
澆水器 淘寶隨便找的 99元,選擇它是因為這個方便改造,有一個可以拆卸的電池盒方便塞開發板和繼電器,按鈕是機械式的,可以通過繼電器短接模擬按鈕效果進行控制,並且有一個手動澆水的功能,也就是按鈕摁一下就澆水,再摁一下就關閉,我們從這個功能下手。
(寫文章的時候這東西已經被我拆掉了,就拿淘寶的圖湊活一下吧,圖上按的中間按鈕就是我們需要接管的按鈕)
(((((((((((っ•ω•)っ Σ(σ`•ω•´)σ 起飛!
改造接線
硬體都到了之後就開始改造電路!
控制電路:
澆水器面板中間的按鈕就是手動控制按鈕下降沿觸發,而我們在這裡使用了一個繼電器常開端接到按鈕上,當開發板 12號 IO 口給繼電器電壓時,繼電器常開端閉合,按鈕被短接,兩端電壓被拉至5V,0.1S後斷開,電壓拉低,下降沿觸發。
休眠檢測電路:
澆水器中有一個10S左右沒有控制就進入休眠狀態的設定我們沒辦法修改,進入休眠狀態後需要一個額外的觸發來喚醒澆水器,而澆水器喚醒時,會點亮數碼管,於是就通過 A0 引腳接到數碼管的共陽級,如果檢測到數碼管的共陽級為低電平,就認為澆水器進入休眠狀態,在觸發命令之前額外觸發一次,解除澆水器的休眠狀態。
澆水器工作狀態檢測電路:
澆水器面板通過訊號線來控制下面水泵電機的工作,這裡我通過5號 IO 監控訊號線的電壓來確定電機的工作狀態。
程式碼解析
為了方便講解邏輯,我會打亂程式碼的順序可能還會進行裁剪,要是想直接拿程式碼跑的朋友可以直接去 靈感桌面的祕密寶庫 獲取程式碼,或者直接 clone:
https://gitee.com/inspiration-desktop/DEV-lib-arduino.git
要是連 git 是什麼都不知道,可以參考簡單無腦,上手即用 - 手把手教你使用 智慧紅外溫度感測器程式碼以及依賴的 gitee 庫!
下載或者 clone 程式碼後這次用到的是這個三個資料夾:
cjson:我移植的 cjson 庫,就是標準的 cjson 庫,放到 arduino 安裝目錄下的 libraries 資料夾裡,百度一下 cjson 的函式使用就行了。
libsddc:是我移植自官方的SDDC庫和自己寫的 SDK,也是放入 libraries 資料夾裡就行。裡面是 SDDC 協議的處理函式,我們不用管。
demo 資料夾裡面就是我們各種感測器的 demo 程式碼了:
具體 arduino 使用教程可以看我之前的文章 arduino開發指導 和 手把手帶你 arduino 開發:基於ESP32S 的第一個應用-紅外測溫槍(帶引腳圖)
裝置控制命令:
通過 Spirit 1 的應用程式或者 嗅探器 向感測器裝置傳送的命令:
通過向澆水器傳送 "ON"/"OFF" 的 set 命令可以控制澆水器是否澆水:
{
"method": "set", // 控制澆水器開始/停止澆水
"watering": "ON"/"OFF"
}
通過向澆水器傳送 "watering" 的 get 命令可以獲取澆水器是否有在澆水:
{
"method": "get", // 獲取澆水器工作狀態
"obj": ["watering"]
}
裝置和協議初始化流程:
基於官方 demo 寫的不需要做什麼修改,主要是裝置初始化,管腳配置,和協議初始化部分。
因為涉及到 IO 口的輸入和輸出,所以需要手動配置一下 IO 口狀態。並且建立一個一個訊息佇列來儲存和傳遞收到的命令
void sensor_init()
{
pinMode(water_pin, OUTPUT);
pinMode(sign_pin, INPUT);
pinMode(monitor_pin,INPUT);
// 設定一個訊息佇列來快取命令,防止命令丟失
Message_Queue = xQueueCreate(MESSAGE_Q_NUM, MESSAGE_REC_LEN); //建立訊息Message_Queue
if(Message_Queue == 0)
{
printf("佇列 Message_Queue 建立失敗!\r\n");
}
}
void setup() {
// 這部分主要是協議初始化和裝置初始化,沒有需要修改的地方,詳見gitee庫
}
void loop() {
// 這部分主要是協議初始化和裝置初始化,沒有需要修改的地方,詳見gitee庫
}
配置裝置資訊
這部分程式碼可以配置 WiFi 名字和 WiFi 密碼,要使用的引腳,並且配置裝置在 Spirit 1 上顯示的資訊:
// 依賴度標頭檔案和庫
#include "Arduino.h"
#include <OneButton.h>
#include <WiFi.h>
#include <sddc.h>
#include <cJSON.h>
#include <Wire.h>
#include <SDDC_SDK_lib.h>
#define SDDC_CFG_PORT 680U // SDDC 協議使用的埠號
#define PIN_INPUT 0 // 選擇 IO0 進行控制
#define ESP_TASK_STACK_SIZE 4096
#define ESP_TASK_PRIO 25
#define MESSAGE_Q_NUM 5 // 資料的訊息佇列的數量
#define MESSAGE_REC_LEN 5 // 資料的訊息佇列的長度
static sddc_t *g_sddc;
static const char* ssid = "TP-LINK_54F9C2"; // WiFi 名
static const char* password = "1234567890"; // WiFi 密碼
static const int water_pin = 12; // 澆水器的控制引腳,控制澆水器啟停
static const int sign_pin = A0; // 澆水器的狀態監視引腳,檢視澆水器是否休眠
static const int monitor_pin = 5; // 工作狀態監視引腳,監視澆水器啟停
QueueHandle_t Message_Queue;
static int xTicksToDelay = 5000; // 週期延時時間
OneButton button(PIN_INPUT, true);
這裡填寫裝置的資訊,方便在 Spirit 1 上檢視和尋找你需要的裝置:
/*
* 當前裝置的資訊定義
*/
DEV_INFO dev_info = {
.name = "智慧澆水", // 裝置的名字
.type = "device",
.excl = SDDC_FALSE,
.desc = "ESP-32S",
.model = "1",
.vendor = "inspiration-desktop",
};
回撥函式註冊
這是收到命令後回撥函式註冊的位置,在這裡註冊的函式才能被 SDK 正確的呼叫,執行正確的動作。
因為澆水器 set 命令為 string 型別,所以對應的處理函式 water_set 註冊到 IO裝置物件設定函式與處理方法註冊 中。
/*
* IO裝置物件設定函式與處理方法註冊
*/
IO_DEV_REGINFO io_dev[] = {
{"watering",water_set},
};
而 get 處理函式返回的同樣是 string 型別,所以在 系統物件狀態獲取註冊 中第二個引數選擇 DEV_IO_TYPE,並且註冊 get 處理函式 single_get_sensor。
/*
* 系統物件狀態獲取註冊
*/
DEV_STATE_GET dev_state_get_reg[] = {
{"watering", DEV_IO_TYPE, single_get_sensor},
};
具體 SDK 的解析可以參考 同人逼死官方系列!基於sddc 協議的SDK框架 sddc_sdk_lib 解析 和 同人逼死官方系列!從 DDC 嗅探器到 sddc_sdk_lib 的資料解析
資料獲取與傳送流程
這裡是自己編寫的處理流程 ,可以根據需求自己更改,收到 set 或者 get 後上文註冊的函式,進入對應的處理函式。
收到 set 命令後,通過關鍵字尋找到對應的處理函式 water_set ,判斷命令是否正確(比如說正在澆水的時候,收到一個ON命令),檢測澆水器是否休眠,如果休眠了那在觸發前就喚醒裝置。
而收到 get 命令後進入對應的處理函式 single_get_sensor 通過讀取面板訊號線判斷電機工作狀態,並且返回給 Spirit 1。
/*
* 主動資料上報函式
*/
static void report_sensor()
{
int sensorValue = 0;
cJSON *value;
cJSON *root;
value = cJSON_CreateArray();
root = cJSON_CreateObject();
sddc_return_if_fail(value);
sddc_return_if_fail(root);
// 按格式生成需要的資料
cJSON_AddItemToArray(value, cJSON_CreateString("上報資料 1 ")); // 這裡的字串要和系統物件狀態獲取註冊結構體裡的對應
// cJSON_AddItemToArray(value, cJSON_CreateString("上報資料 2 ")); // 需要上報幾個就新增幾個
cJSON_AddItemToObject(root, "obj", value);
// 傳送資料給 EdgerOS
object_report(root);
cJSON_Delete(value);
}
/*
* 澆水狀態監控函式
*/
static void monitor_task(void *arg)
{
int newval = 1;
int oldval = 1;
// 監控澆水開啟和關閉狀態
while(1)
{
newval = digitalRead(monitor_pin);
if(newval != oldval)
{
report_sensor();
}
oldval = newval;
// 任務建立之後,設定延時週期
delay(100);
}
vTaskDelete(NULL);
}
/*
* 澆水觸發任務
*/
static void button_task(void *arg)
{
char SW[5];
char *value;
BaseType_t err;
if(Message_Queue != NULL)
{
err = xQueueReceive(Message_Queue, &value, portMAX_DELAY );
if(err == pdFALSE)
{
printf("佇列 Message_Queue 資料獲取失敗!\r\n");
}
}
sddc_printf("\nMessage_Queue value: %s!!!!!\n", value);
// 監控電機工作狀態
if(digitalRead(monitor_pin))
{
strcpy(SW,"OFF");
}else
{
strcpy(SW,"ON");
}
// 如果命令要求與電機當前工作狀態一致就不處理
if(0 != strcmp(value,SW) && (value != NULL))
{
// 判斷機器是否休眠如果休眠了就行喚醒機器
delay(100);
int a = analogRead(sign_pin);
sddc_printf("\n a1 == : %d!!!!!\r\n", analogRead(sign_pin));
if(!(a > 1) && (0 == strcmp(value,"ON")))
{
Serial.println("喚醒");
digitalWrite(water_pin, HIGH);
delay(100);
digitalWrite(water_pin, LOW);
delay(100); // 因為是下降沿觸發,所以加延遲保證下降沿不會被後面的命令沖掉
}
// 觸發澆水器開或者關
Serial.println("觸發");
digitalWrite(water_pin, HIGH);
delay(100);
digitalWrite(water_pin, LOW);
delay(100);
}
vTaskDelete(NULL);
}
/*
* 澆水器控制函式
*/
sddc_bool_t water_set(const char* value)
{
BaseType_t err;
sddc_printf("\niot_pi_on_message: %s!!!!!\n", value);
if((Message_Queue != NULL)&&(value))
{
// 通過訊息佇列儲存收到的命令,防止命令丟失
err = xQueueSendToFront(Message_Queue,&value,0 );
if(err == pdFALSE)
{
printf("佇列 Message_Queue 已滿,資料傳送失敗!\r\n");
}
}
// 建立電機觸發任務,防止阻塞message_ack回覆
xTaskCreate(button_task, "button_task", ESP_TASK_STACK_SIZE, NULL, ESP_TASK_PRIO, NULL);
return SDDC_TRUE;
}
/*
* 填寫澆水狀態
*/
sddc_bool_t single_get_sensor(char *objvalue, int value_len) // 注意函式名要和上文註冊的函式名保持一致,當收到 get 訊息之後通過關鍵字就能找到並且呼叫這個函式
{
if(digitalRead(monitor_pin))
{
strncpy(objvalue, "OFF", value_len);
}else
{
strncpy(objvalue, "ON", value_len);
}
return SDDC_TRUE;
}
程式碼寫完之後燒錄進去就完事了,和之前完全一樣,點一下儲存,然後上傳OK,具體可以看之前的文件,我就懶得再寫一遍啦 (/ω\)。
總結
智慧澆水器製作完成!加上之前製作的土壤溼度感測器,和 Spirit 1 就完成了我們智慧澆花場景的搭建。接下來就寫一個智慧澆花的應用就能完美的解決忘記澆水的麻煩!