语音播报加入预警系统
引言
引入语音警报
我们前一章, 已经把jq8900-16p模块, 单独进行了测试, 可以通过发送命令, 让模块播报设定好的声音。那么语音播报, 在预警系统中, 也必不可少, 我们现在有了led灯光警报,如果主人在睡觉, 是不能及时的进行得知的. 所以语音播报, 可以从声音层面进行预警,提高家居安全性
先调试单独解耦(点击跳转)
本博客调试完成的工程:
https://wwyz.lanzoul.com/ijIUb27fhu2j
点击下载
讲解方式和目的
其实单独解耦, 就说明, 我们可以调用底层命令, 然后进行语音播报就可以了。那也意味着我们可以忽略语音播报的底层, 专注在预警系统中, 特定的时间, 去调用播报语音就行了。
所以我们这里注重讲解, 如何对预警系统中, 所需语音进行配置分析, 以及在什么时候去报警, 报警情况的分析。
报警情况分析
(1)分析预警情况
通过分析ararm_monitor 函数,得知, 我们分成了四种情况
① //(1)温度正常, 湿度异常 result = 1;
② //(2)温度异常,湿度正常 result = 2;
③ //(3)温度,湿度都异常 result = 3;
④ //(4)温度,湿度都正常 result = 0;
(2)分析报警语音
①温度正常, 湿度异常 result = 1:
湿度异常,开启电机除湿,蓝灯警报
② 温度异常,湿度正常 result = 2:
温度异常,开启电机降温,红灯警报
③ 温度,湿度都异常 result = 3:
温湿度均异常, 开启电机降温除湿,红蓝灯警报
④ 环境正常的情况下(result == 0):
环境恢复正常,警报解除
(3)调用语音
我们分别对这四种情况, 在预警处理的函数里面, 进行对应的播报
alarm_voice_Set(status_normal); //环境正常
alarm_voice_Set(humidity_high);//只有湿度异常
alarm_voice_Set(temperature_high);//只有温度异常
alarm_voice_Set(both_high);//温湿度都异常
函数内调用语音播报
void ararm_deal(int alarm_mode)
{
switch(alarm_mode)
{
case 0:
humi_led_Set(blue_led_OFF);
temp_led_Set(red_led_OFF);
Fun_Set(FUN_OFF);
alarm_voice_Set(status_normal);
break;
case 1:
humi_led_Set(blue_led_ON);
temp_led_Set(red_led_OFF);
Fun_Set(FUN_ON);
alarm_voice_Set(humidity_high);
break;
case 2:
temp_led_Set(red_led_ON);
humi_led_Set(blue_led_OFF);
Fun_Set(FUN_ON);
alarm_voice_Set(temperature_high);
break;
case 3:
humi_led_Set(blue_led_ON);
temp_led_Set(red_led_ON);
Fun_Set(FUN_ON);
alarm_voice_Set(both_high);
break;
default: break;
}
}
(4)调用语音, 底层思考
结合我们led灯调用和fun调用, 我们为了保证实时性,其实是在一直刷新然后调用器件控制函数的. 如果我们直截了当的, 直接调用器件底层, 发送播放命令, 就会一直重复启动。
我们之前的处理办法都是, 先判断一下器件的状态,如何和我们所需设置的状态是一致的, 那么我们就无需操作了。不仅保证了实时性,也可以不用重复启动了。
只是我们不同的是, led灯何fun只有两种状态, 开或者关, 我们语音播报, 有4种状态, 之前我们都是用布尔状态, 来表示开关,这次, 我们要拓展语音播报的状态了。
typedef enum {
status_normal = 0, // 温度和湿度恢复正常 0
humidity_high = 1, // 湿度异常 1
temperature_high = 2, // 温度异常 2
both_high = 3 // 都异常 3
}VoiceStatus;
typedef struct {
VoiceStatus voice_status;
} AlarmVoiceInfo;
利用枚举 , 我们的状态不仅可以确定, 还可以直接传入, 避免错乱, 增强了可读性. 对于枚举不熟悉的小伙伴, 请看洋桃电子的枚举视频
(5)传入预警状态, 调用底层语音播报
我们在预警的时候, 调用了 void alarm_voice_Set(int status);
我们还是那句话, 先判断所需状态, 是否和当前状态一致, 意思就是,我们要播放的语音, 是否刚才已经赋值播放了。
再直白的说,每一次时间段, 都是上面的四种情况, 我们是在每分每秒的检测, 然后调用语音播报的,如果直接发送指令, 就会重复陷入启动器件的循环之中, 但是我们每分每秒检测是为了保证实时性, 所以是不可缺少了, 那么我们就要从为了避免重复启动入手, 也就是我们不能直接反复调用发送指令, 其实我们每个预警情况的时间段, 只需要调用一次语音就行了。
所以当我们切换状态的时候, 再播放就可以了, 所以当传入的状态和之前播报的状态不一致的时候, 再进行播报所需状态就可以了, 同时, 我们记得把对应的状态更新。
![image-20240814114411092](https://img-blog.csdnimg.cn/img_convert/cabed99938d889a730d0c8fa2647b7a7.png)
对应的语音设置函数代码:
void alarm_voice_Set(int status)
{
//当切换状态的时候, 播放
if(status != alarmInfo.voice_status)
{
switch(status)
{
case 0: //环境恢复正常,警报解除
SendData(0x0a);
SendData(0x01);
SendData(0x0b);
alarmInfo.voice_status = status_normal;
break;
case 1: //湿度异常,开启电机除湿,蓝灯警报
SendData(0x0a);
SendData(0x02);
SendData(0x0b);
alarmInfo.voice_status = humidity_high;
break;
case 2: //温度异常,开启电机降温,红灯警报
SendData(0x0a);
SendData(0x03);
SendData(0x0b);
alarmInfo.voice_status = temperature_high;
break;
case 3:
SendData(0x0a); //温湿度均异常, 开启电机降温除湿,红蓝灯警报
SendData(0x04);
SendData(0x0b);
alarmInfo.voice_status = both_high;
break;
default: break;
}
}
}
工程代码实操
在原有工程上加入jq8900文件, 以及在预警处理地方调用播放语音播报
jq8900单独解耦工程:
https://wwyz.lanzoul.com/iHJhs27d9a2j
预警系统工程:
https://wwyz.lanzoul.com/ieFtH277kble
(1)首先分别打开这两个工程, 然后直接开启复制黏贴
![image-20240814171927318](https://img-blog.csdnimg.cn/img_convert/a79e2cafc05a2e435ece27337666bddf.png)
(2)把jq8900-16p的 Source_code里面的jq8900文件夹, 复制进入预警系统里面
(3)然后我们接着把这个文件夹里面的代码,再加入工程
(4)把文件夹加入环境变量
(5)接着我们开始构建预警系统的语音播报
右键jq8900文件夹, 然后添加 alarm_voice.c 和 alarm_voice.h文件
(6)然后我们开始逐步构建语音播报预警文件
其实也很简单, 就是模仿 led和fun的状态设置, 只不过这次我们状态不止两种, 而是四种, 所以我们状态设置, 使用的枚举结构体
预警系统状态结构体(ctrl 加鼠标左键,快速跳转)
<1>结构体配置
alarm_voice.h结构体代码
typedef enum {
status_normal = 0, // 温度和湿度恢复正常 0
humidity_high = 1, // 湿度异常 1
temperature_high = 2, // 温度异常 2
both_high = 3 // 都异常 3
}VoiceStatus;
typedef struct {
VoiceStatus voice_status;
} AlarmVoiceInfo;
<2>初始化
我们配置一下初始化, 因为毕竟是利用jq8900的底层, 在alarm_vice.c里面包含一下JQ8900.h, 然后调用一下jq8900的初始化函数, 同时也可以设置一下音量, 同时我们设置一下默认情况, 一般是正常情况(这里我们要考虑一下初始的阈值,保证器件稳定后, 能正常运行即可)
注意这里调用的是枚举里面的变量, 所以可以直接使用, 为了保证可读性和安全性
#include "alarm_voice.h"
#include "JQ8900.h"
AlarmVoiceInfo alarmInfo;
void alarm_voice_Init(void)
{
Init_One_line_Uart();
//设置音量
SendData(0x0a);
SendData(0x02);
SendData(0x00);
SendData(0x0c);
alarmInfo.voice_status = status_normal; //初始情况, 默认正常
}
<3> 语音警报设置
void alarm_voice_Set(int status)
{
//当切换状态的时候, 播放
if(status != alarmInfo.voice_status)
{
switch(status)
{
case 0: //环境恢复正常,警报解除
SendData(0x0a);
SendData(0x01);
SendData(0x0b);
alarmInfo.voice_status = status_normal;
break;
case 1: //湿度异常,开启电机除湿,蓝灯警报
SendData(0x0a);
SendData(0x02);
SendData(0x0b);
alarmInfo.voice_status = humidity_high;
break;
case 2: //温度异常,开启电机降温,红灯警报
SendData(0x0a);
SendData(0x03);
SendData(0x0b);
alarmInfo.voice_status = temperature_high;
break;
case 3:
SendData(0x0a); //温湿度均异常, 开启电机降温除湿,红蓝灯警报
SendData(0x04);
SendData(0x0b);
alarmInfo.voice_status = both_high;
break;
default: break;
}
}
}
<4>头文件包含子函数,以及声明状态变量
extern AlarmVoiceInfo alarmInfo;
void alarm_voice_Init(void);
void alarm_voice_Set(int status);
<5>alarm_voice.c 和 alarm_voice.h 代码备份
alarm_voice.c
#include "alarm_voice.h"
#include "JQ8900.h"
AlarmVoiceInfo alarmInfo;
void alarm_voice_Init(void)
{
Init_One_line_Uart();
//设置音量
SendData(0x0a);
SendData(0x02);
SendData(0x00);
SendData(0x0c);
alarmInfo.voice_status = status_normal; //初始情况, 默认正常
}
void alarm_voice_Set(int status)
{
//当切换状态的时候, 播放
if(status != alarmInfo.voice_status)
{
switch(status)
{
case 0: //环境恢复正常,警报解除
SendData(0x0a);
SendData(0x01);
SendData(0x0b);
alarmInfo.voice_status = status_normal;
break;
case 1: //湿度异常,开启电机除湿,蓝灯警报
SendData(0x0a);
SendData(0x02);
SendData(0x0b);
alarmInfo.voice_status = humidity_high;
break;
case 2: //温度异常,开启电机降温,红灯警报
SendData(0x0a);
SendData(0x03);
SendData(0x0b);
alarmInfo.voice_status = temperature_high;
break;
case 3:
SendData(0x0a); //温湿度均异常, 开启电机降温除湿,红蓝灯警报
SendData(0x04);
SendData(0x0b);
alarmInfo.voice_status = both_high;
break;
default: break;
}
}
}
alarm_voice.h
#ifndef _ALARM_VOICE_H
#define _ALARM_VOICE_H
#include "stm32f10x.h"
typedef enum {
status_normal = 0, // 温度和湿度恢复正常 0
humidity_high = 1, // 湿度异常 1
temperature_high = 2, // 温度异常 2
both_high = 3 // 都异常 3
}VoiceStatus;
typedef struct {
VoiceStatus voice_status;
} AlarmVoiceInfo;
extern AlarmVoiceInfo alarmInfo;
void alarm_voice_Init(void);
void alarm_voice_Set(int status);
#endif
<5>预警系统调用语音
① 先包含预警系统头文件
#include "alarm_voice.h"
② main函数内初始化语音文件
alarm_voice_Init();
③ 然后我们只需在预警的时候调用就可以了
main.c代码备份:
#include "stm32f10x.h" // Device header
#include "sys.h"
#include "delay.h"
#include "dht11.h"
#include "OLED.h"
#include "humi_led.h"
#include "temp_led.h"
//联网配置文件
#include "uart.h"
#include "esp8266.h"
#include "onenet.h"
#include "fun.h"
#include "alarm_voice.h"
#define ESP8266_ONENET_INFO "AT+CIPSTART=\"TCP\",\"mqtts.heclouds.com\",1883\r\n"
uint8_t temp; //读取的环境温度
uint8_t humi; //读取的环境湿度
uint8_t temp_th; //设置的温度阈值
uint8_t humi_th; //设置的湿度阈值
_Bool set_limit; //管理员是否设置阈值
led小灯初始化
//void led_init(void);
int deal_mode;
unsigned short timeCount = 0; //发送间隔变量
int ararm_monitor(uint8_t temp,uint8_t humi,uint8_t temp_th, uint8_t humi_th);
void ararm_deal(int alarm_mode);
unsigned short timeCount; //发送间隔变量
int8_t Speed; //定义速度变量
//调试变量
int result;
int main()
{
unsigned char *dataPtr = NULL; //标志位, 是否接受到onenet发送的数据
//手动设置环境温度(模拟上帝)
temp = 0; //温度是28度
humi = 0; //湿度是60%
//手动设置阈值(代替远程)
temp_th = 80; //温度阈值是30度
humi_th = 80; //湿度阈值是60%
//测试变量: 管理设置阈值符号位
set_limit = 1;
//滴答定时器
Delay_Init();
//dht11初始化
while(DHT11_Init())
{
// printf("DHT11 Error \r\n");
OLED_ShowString(0,48, "DHT11 Error", 8);
OLED_Update();
DelayMs(2000);
}
uart1_init(115200);//串口1,波特率115200,调试用
uart2_init(115200); //串口2,驱动ESP8266用
alarm_voice_Init();
ESP8266_Init();//wifi初始化
while(ESP8266_SendCmd(ESP8266_ONENET_INFO, "CONNECT"))
DelayXms(500);
while(OneNet_DevLink()) //接入OneNET
DelayXms(500);
OneNET_Subscribe(); //解析onenet下发指令
//oled初始化
OLED_Init();
Fun_Init();
Speed = 20;
humi_led_Init();
temp_led_Init();
while(1)
{
//(2)读取环境信息 (手动输入代替器件读取)
//if(过了100毫秒) 每隔100毫秒,读取一次, 保证实时性,减轻负担
if(++timeCount >= 100)
{
DHT11_Read_Data(&temp,&humi);
OLED_ShowChinese(0, 0, "温度");
OLED_ShowChinese(0, 24, "湿度:");
OLED_Printf(48,0,OLED_8X16,"%2d",temp);
OLED_Printf(48,24,OLED_8X16,"%2d",humi);
OLED_ShowChinese(80,0 , "℃");
OLED_ShowChinese(80,24 , "%");
OLED_Printf(48,48,OLED_8X16,"%2d",result);
OLED_Update();
//联网上传
OneNet_SendData();
timeCount = 0;
ESP8266_Clear();
}
//检测是否有下发信息
dataPtr = ESP8266_GetIPD(0);
if(dataPtr != NULL)
{
OneNet_RevPro(dataPtr);
// UsartPrintf(USART_DEBUG, "6666666666\r\n");
}
delay(0xAFC80);//延时10ms
//后面跟 1ms的延时, 我们一切从简,先留着
//(3)每时每刻判断环境信息是否异常
//判断预警处理
deal_mode = ararm_monitor(temp,humi,temp_th,humi_th);
//根据器件预警信息, 进行应急操作
ararm_deal(deal_mode);
}
}
int ararm_monitor(uint8_t temp,uint8_t humi,uint8_t temp_th, uint8_t humi_th)
{
//(1)温度正常, 湿度异常
if(temp < temp_th && humi >= humi_th)
{
result = 1;
}
else //(2)温度异常,湿度正常
if(temp >= temp_th && humi < humi_th)
{
result = 2;
}
else //(3)温度,湿度都异常
if(temp >= temp_th && humi >= humi_th)
{
result = 3;
}
else //(4)温度,湿度都正常
if(temp < temp_th && humi < humi_th)
{
result = 0;
}
return result;
}
void ararm_deal(int alarm_mode)
{
switch(alarm_mode)
{
case 0:
humi_led_Set(blue_led_OFF);
temp_led_Set(red_led_OFF);
Fun_Set(FUN_OFF);
alarm_voice_Set(status_normal);
break;
case 1:
humi_led_Set(blue_led_ON);
temp_led_Set(red_led_OFF);
Fun_Set(FUN_ON);
alarm_voice_Set(humidity_high);
break;
case 2:
temp_led_Set(red_led_ON);
humi_led_Set(blue_led_OFF);
Fun_Set(FUN_ON);
alarm_voice_Set(temperature_high);
break;
case 3:
humi_led_Set(blue_led_ON);
temp_led_Set(red_led_ON);
Fun_Set(FUN_ON);
alarm_voice_Set(both_high);
break;
default: break;
}
}
工程演示:
https://www.bilibili.com/video/BV1ZkeneeEWd/?spm_id_from=333.999.0.0