文章目录

  • 前言
  • 硬件选择
  • 改造接线
  • 代码解析
  • 设备控制命令:
  • 设备和协议初始化流程:
  • 配置设备信息
  • 回调函数注册
  • 数据获取与发送流程
  • 总结



前言

上次 土壤湿度传感器 完成之后,就立下一个 flag 要搭建一个智慧浇水的智能场景,现在终于有时间填坑了!(o゚▽゚)o

智慧浇水场景的核心设备有三个:
检测土壤状态的:土壤湿度传感器 通过这个传感器来获取土壤信息,作为是否浇水的依据。
智能浇水器:执行装置,通过 Spirit 1 控制。
Spirit 1

这次就来制作智慧浇水的智能场景的核心: 智能浇水器,我准备买一个便宜的傻不拉几的浇水器自己改造一下,想办法给他连上脑子。

esp32单片机简介 esp32 diy_esp32单片机简介


主要交互流程如下图:

esp32单片机简介 esp32 diy_esp32单片机简介_02

(σ゚∀゚)σ…:*☆哎哟不错哦,是不是很厉害啊!


硬件选择

万年不变的 安信可的 ESP32S ,别问,问就是便宜才 24元。

继电器,因为不清楚浇水器电路情况保险起见,使用了继电器进行隔离,4.5元

浇水器 淘宝随便找的 99元,选择它是因为这个方便改造,有一个可以拆卸的电池盒方便塞开发板和继电器,按钮是机械式的,可以通过继电器短接模拟按钮效果进行控制,并且有一个手动浇水的功能,也就是按钮摁一下就浇水,再摁一下就关闭,我们从这个功能下手。

esp32单片机简介 esp32 diy_初始化_03


(写文章的时候这东西已经被我拆掉了,就拿淘宝的图凑活一下吧,图上按的中间按钮就是我们需要接管的按钮)

(((((((((((っ•ω•)っ Σ(σ`•ω•´)σ 起飞!

改造接线

硬件都到了之后就开始改造电路!

控制电路:

esp32单片机简介 esp32 diy_物联网_04


浇水器面板中间的按钮就是手动控制按钮下降沿触发,而我们在这里使用了一个继电器常开端接到按钮上,当开发板 12号 IO 口给继电器电压时,继电器常开端闭合,按钮被短接,两端电压被拉至5V,0.1S后断开,电压拉低,下降沿触发。休眠检测电路:

esp32单片机简介 esp32 diy_javascript_05

浇水器中有一个10S左右没有控制就进入休眠状态的设置我们没办法修改,进入休眠状态后需要一个额外的触发来唤醒浇水器,而浇水器唤醒时,会点亮数码管,于是就通过 A0 引脚接到数码管的共阳级,如果检测到数码管的共阳级为低电平,就认为浇水器进入休眠状态,在触发命令之前额外触发一次,解除浇水器的休眠状态。

浇水器工作状态检测电路:

esp32单片机简介 esp32 diy_初始化_06

浇水器面板通过信号线来控制下面水泵电机的工作,这里我通过5号 IO 监控信号线的电压来确定电机的工作状态。

代码解析

为了方便讲解逻辑,我会打乱代码的顺序可能还会进行裁剪,要是想直接拿代码跑的朋友可以直接去 灵感桌面的秘密宝库 获取代码,或者直接 clone:

https://gitee.com/inspiration-desktop/DEV-lib-arduino.git

要是连 git 是什么都不知道,可以参考简单无脑,上手即用 - 手把手教你使用 智能红外温度传感器代码以及依赖的 gitee 库! 下载或者 clone 代码后这次用到的是这个三个文件夹:

esp32单片机简介 esp32 diy_初始化_07


cjson:我移植的 cjson 库,就是标准的 cjson 库,放到 arduino 安装目录下的 libraries 文件夹里,百度一下 cjson 的函数使用就行了。

libsddc:是我移植自官方的SDDC库和自己写的 SDK,也是放入 libraries 文件夹里就行。里面是 SDDC 协议的处理函数,我们不用管。

demo 文件夹里面就是我们各种传感器的 demo 代码了:

esp32单片机简介 esp32 diy_javascript_08


具体 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},
};

 

数据获取与发送流程

这里是自己编写的处理流程 ,可以根据需求自己更改,收到 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 就完成了我们智能浇花场景的搭建。接下来就写一个智能浇花的应用就能完美的解决忘记浇水的麻烦!