文章目录
- 前言
- 硬件选择
- 改造接线
- 代码解析
- 设备控制命令:
- 设备和协议初始化流程:
- 配置设备信息
- 回调函数注册
- 数据获取与发送流程
- 总结
前言
上次 土壤湿度传感器 完成之后,就立下一个 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},
};
数据获取与发送流程
这里是自己编写的处理流程 ,可以根据需求自己更改,收到 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 就完成了我们智能浇花场景的搭建。接下来就写一个智能浇花的应用就能完美的解决忘记浇水的麻烦!