前言
上次 土壤濕度傳感器 完成之后,就立下一個 flag 要搭建一個智慧澆水的智能場景,現在終於有時間填坑了!(o゚▽゚)o
智慧澆水場景的核心設備有三個:
檢測土壤狀態的:土壤濕度傳感器 通過這個傳感器來獲取土壤信息,作為是否澆水的依據。
智能澆水器:執行裝置,通過 Spirit 1 控制。
Spirit 1
這次就來制作智慧澆水的智能場景的核心: 智能澆水器,我准備買一個便宜的傻不拉幾的澆水器自己改造一下,想辦法給他連上腦子。

主要交互流程如下圖:

(σ゚∀゚)σ..:*☆哎喲不錯哦,是不是很厲害啊!
硬件選擇
萬年不變的 安信可的 ESP32S ,別問,問就是便宜才 24元。
繼電器,因為不清楚澆水器電路情況保險起見,使用了繼電器進行隔離,4.5元
澆水器 淘寶隨便找的 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 就完成了我們智能澆花場景的搭建。接下來就寫一個智能澆花的應用就能完美的解決忘記澆水的麻煩!
