物联网应用创新大赛] 基于 TencentOS 微小空间的智能家居主控系统
PPT:
视频(演示视频在最后):
视频内容:
大家好,我是黑白方圆,我将要讲解的是智能家居总控系统模型,这是我的参赛题目
在这里呢,我将把内容分成4部分讲解
首先,我要先讲一下概念,在传统的生活方式中呢,我们都是手动打开电器,亲自去检查门有没有关好,亲手去拉窗帘。 而到了现在,我们有了各种各样的智能电器,例如智能电视,智能门锁这些。 而现实中这些设备往往对普通人来说是可望不可即的存在,因为它们造价比较昂贵,比较少的人消费的起。 而有没有一种居中的方法让他们之间调和呢,于是我就设计出了这个智能管家设备,也就是 智能 家居总控系统。它的主要特点是可以智能的控制家里的各种设备,例如电视啊,窗帘这些,还可以实时的监控家里的物理状态,让我们在外面可以简单的了解到家里的情况,就例如门到底锁好没有,空调有没有关。 而这个 智能 家居总控系统 相比于智能家电最大的不同在于它只有一个mcu, 一个mcu控制整个家庭的设备,而不必给每个设备都装上mcu和联网模块。 所以,这个智能 家居总控系统 相当于起到了一个管家的角色
这是整个系统的框架 由于腾讯还没有智能音箱,所以我在这里选择了天猫精灵作为这个系统的语音交互部分,而小程序负责和用户进行界面交互,这个总控设备则属于硬件部分,他的主要通讯原理是,语音从天猫精灵走到个人服务器,再通过腾讯云iothub发送指令到总控设备,小程序则需要先登录,获取到自己绑定过的总控设备id,再直接通过腾讯云iothub发送指令到设备。 总控设备只会与iothub直接通讯
硬件部分,总控设备可以通过继电器控制灯,电视,或者是其他各种电器的开关,它可以做成智能插座的样子,也可以直接接管所有插座的火线零线,以达到控制开关的目的。插件是可以扩展的,可以自己按需添加插件,例如可以直接插上一个窗帘控制的模块,通过布线进行通讯控制,再或者增加一个红外发射模块,达到控制电视空调的效果,当然它可以添加传感器,例如检测火灾,温湿度是否舒适,也可以在门框装上一个按键简单的判断门是否锁好了等等,它是可以按需扩展的
我使用的开发板是腾讯云提供的evb mx 加
这个系统的模型用到了这些配件
在这个系统中呢,我在iothub创建了俩个产品,一个由硬件设备登录,允许有无数个设备,另一个是网关,这个网关里面只有一个设备,由我个人的服务器登录。规则引擎这里我创建了一条规则,把所有硬件设备上报到event的数据到转发到我的服务器,服务器可以通过这些数据执行相应的操作
服务器使用了python语言进行搭建,它在初始化的时候像刚刚说的那样连上了mqtt,mqtt收到消息的时候可以进行处理,这里火灾可以通过小程序的订阅消息功能进行提醒用户,由于我多次申请模板失败所以这里没有写任何代码,实际中可以通过电话提醒用户
阿里语音平台负责定义某些意图让个人服务器可以正确的收到语音指令,在实际中我们可以在硬件部分添加麦克风然后接入腾讯云的语音识别可以得到更好的效果,
这是微信小程序的三个界面,是从腾讯ostiny 提供的demo中二次修改而成的,主界面如图中所示,可以方便的操作各个设备,也可以为单独的开关自定义别名
左边是配网功能,总控设备在不能联网时会转换成ap模式,小程序可以连上它的热点然后通过udp广播发送wifi密码还有账号的信息,总控设备联网后自动绑定对应的账号。 右边是绑定天猫精灵的功能,小程序向个人服务器发送绑定请求,然后服务器返回小程序一个临时的id,然后对天猫精灵说出这个id,服务器收到后即可完成绑定
这是我制作出来的模型,由于我是一名学生党,经费有限,所以只能做出一个简单的模型。 继电器这里在实际中可以接入火线,由于具有危险性我使用了普通的led灯,3.3v板载电源,下面这个是模拟的窗帘开关插件
我的作品讲解完毕,谢谢观看,以下是模型的演示视频
补充:后续可添加情景模式,例如按时间触发,或按条件触发行为, 如凌晨自动打开空调等等
系统框架
硬件部分代码:
主线程
while (1) {
while(peiwang(err)==0);//死循环检测联网
err=mqtt_basic_thread();
printf("This is a mqtt demo %d!rn",err);
tos_task_delay(1000);
}
配网子程序
int peiwang(int err){
int result=0;
extern int esp8266_net_mode_set(sal_net_mode_t mode);
esp8266_net_mode_set(SAL_NET_MODE_STA_AP);
socket_id_0 = esp8266_connect2("255.255.255.255", "8888", TOS_SAL_PROTO_UDP,"9527");
//相当于AT指令 AT+CIPSTART="UDP","255.255.255.255",8888,9527rn
int recv_len = -1;
tos_task_delay(500);
tos_at_cmd_exec(&echo, 3000, "AT+CWSAP="xryunkong","123404321",1,3rn");
while (1) {
if(ssid[0]!=0 && psk[0]!=0){
//曾连接,暂时断网
esp8266_join_ap(ssid, psk);
if (ping()){
//重新联网成功
esp8266_net_mode_set(SAL_NET_MODE_STA);
result=1;
break;
}
}
recv_len = tos_sal_module_recvfrom(socket_id_0, recv_data_0, sizeof(recv_data_0));
if (recv_len < 0) {
result=0;
break;
} else if (recv_len > 0) {
tos_task_delay(200);
recv_data_0[recv_len] = 0;
printf("task0: receive len: %dnmsg from remote: %sn", recv_len, recv_data_0);
if(jiexi(recv_data_0)){//提取密码信息
tos_sal_module_send(socket_id_0,(const void*)"{"xrcode":1}", strlen("{"xrcode":1}"));
tos_task_delay(100);
tos_sal_module_send(socket_id_0,(const void*)"{"xrcode":1}", strlen("{"xrcode":1}"));
tos_task_delay(100);
//收到密码回复
esp8266_join_ap(ssid, psk);
}
}
tos_sleep_ms(2000);
}
tos_sal_module_close(socket_id_0);
return result;
}
数据解析:
// callback when MQTT msg arrives
static void on_message_callback(void *pClient, MQTTMessage *message, void *userData)
{
if (message == NULL) {
return;
}
Log_i("Receive Message With topicName:%.*s, payload:%.*s",
(int) message->topic_len, message->ptopic, (int) message->payload_len, (char *) message->payload);
cJSON* cjson = cJSON_Parse((char *)message->payload);
cJSON *cjsonret=NULL;
int motor;
int one;
char *fromwhere;
cjsonret=cJSON_GetObjectItem(cjson,"from");
if(cjsonret!=NULL)
{
fromwhere=cJSON_GetStringValue(cjsonret);//查看消息是来自天猫精灵还是小程序
printf("from=%sn",fromwhere);
}
cjsonret=NULL;
cjsonret=cJSON_GetObjectItem(cjson,"motor");
if(cjsonret!=NULL)
{
motor=cjsonret->valueint;
printf("motor=%dn",motor);
//电机操作使用队列消息处理,使电机操作过程中不会被打断
if(motor==1) tos_msg_q_post(&moto,(void *)1);
if(motor==0) tos_msg_q_post(&moto,(void *)0);
changestate(fromwhere,"motor",motor);//写入历史记录 更新状态 更新设备影子
}
cjsonret = NULL;
cjsonret = cJSON_GetObjectItem(cjson,"one"); //继电器第一个开关
if(cjsonret!=NULL)
{
one=cjsonret->valueint;
printf("one=%dn",one);
HAL_GPIO_WritePin(GPIOB, Pinone, one);
changestate(fromwhere,"one",one);
}
//此处省略继电器第N个开关,代码几乎同上
}
电机操作(窗帘)
void motor(int op){
int pin;
if(op==1)pin=zhengpin;
if(op==0)pin=fanpin;
HAL_GPIO_WritePin(GPIOB,pin,1);
tos_sleep_ms(delaytime);
HAL_GPIO_WritePin(GPIOB,pin,0);
return;
}
void opmotor(void *onoff){
//此处为一个实时Thread
void *msg;
int err,op;
Init_Pin(GPIOB,zhengpin,GPIO_MODE_OUTPUT_PP);
Init_Pin(GPIOB,fanpin,GPIO_MODE_OUTPUT_PP);
err=tos_msg_q_create(&moto, test_msg_q_pool_00, 5);
if(err!=K_ERR_NONE )
printf("tos_mutex_create error %d!!!!!!!!!!!!!!!",err);
while(1){
err=tos_msg_q_pend(&moto, &msg, TOS_TIME_FOREVER);
op=(int)msg;
motor(op);
printf("delay done onoff %dn",op);
}
}
传感器检测
int door=0;
int wendu=0;
int shidu=0;
void changedoor(int state){
door=state;
changestate(NULL,"door",door);//不写入历史记录,更新影子
}
void action(void *arg){
//一个普通Thread
Init_Pin(IA1_Light_GPIO_Port,IA1_Light_Pin,GPIO_MODE_OUTPUT_PP);
Init_Pin(GPIOC,GPIO_PIN_13,GPIO_MODE_OUTPUT_PP);
tos_task_delay(1000);
DHT11_Data_TypeDef dataaaa;
int tempdoor=0,dcount=0;
while(1){
tos_task_delay(2000);//每俩秒检测一次
//printf("running......n");
DHT11_Read_TempAndHumidity(&dataaaa);
if(wendu<200){//超过200则读取数据错误
wendu=dataaaa.temp_high8bit;
shidu=dataaaa.humi_high8bit;
}
if(wendu>45 & shidu < 40){//简单地判断为火情危险
if(warn==0){//若未上报
sprintf(buff1,"{"method":"fire","w":%d,"s":%d}",wendu,shidu);
if(_publish_msg(clienttwo,"event",buff1,QOS1)==0)//规则引擎将event转发到个人服务器
warn=1;//已上报
}
}
if(wendu <40 & shidu >50) warn=0;
tempdoor=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_12);//简单地使用按钮来判断门是否锁上
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,!tempdoor);//LED灯
if(tempdoor!=door){//若门变更状态长达6秒则更改门状态
dcount++;
if(dcount>3){
changedoor(tempdoor);//上报
}
}else dcount=0;
}
return;
}
联网上报
if(openid[0]==0)
sprintf(buff,"{"method":"online","devname":"%s"}",sg_devInfo.device_name);
else{
sprintf(buff,"{"method":"online","devname":"%s","openid":"%s"}",sg_devInfo.device_name,openid);
openid[0]=0;
}
_publish_msg(client,"event",buff,QOS1);
服务器入口代码:
if __name__ == '__main__':
def on_message_come(msg):
content=json.loads(msg.payload.decode())
print(msg.topic + " :" + msg.payload.decode())
#{'method':online,'devname':'123'}
if content['method']=='online':
print(content['devname'],'上线了')
bind.devonline(content['devname'])
try:
bind.addbind(content['openid'],content['devname']) #直接尝试绑定
except:
return
if content['method']=='fire':
print("火灾")
pass
serwg=mqtt.mqttclient("YEAW8WRR0W",'123','******此处打码******')
'''
服务器也是一个mqtt客户端, 产品类型为网关
硬件设备上报到event的数据由规则引擎转发到服务器
'''
serwg.on_subscribe('data', on_message_come)
time.sleep(1)
serwg.on_subscribe('data', on_message_come)
urllist.update({'tianmao':tianmao,'onLogin':onlogin,'bind':sbind,'tmbind':tmbind,'chname':thing.setbingThing})
if len(sys.argv)==2:
app.run(debug=True,host='0.0.0.0',port=8080, ssl_context=('server.crt', 'server.key'))
小程序(配网)部分代码:
onLoad: function (options) {
//this.bind()
wx.authorize({
scope: 'scope.userLocation',
success() {
// 用户已经同意小程序使用录音功能,后续调用 wx.startRecord 接口不会弹窗询问
}
})
wx.startWifi({
})
//获取正在连接的wifi名称
wx.getConnectedWifi({
fail: function (res) {
switch (res.errCode) {
case 12005:
wx.showModal({
title: '提示',
content: '请先开启wifi',
showCancel: false
})
wx.navigateBack({
})
break;
case 12006:
wx.showModal({
title: '提示',
content: '请先开启GPS',
showCancel: false
})
wx.navigateBack({
})
break;
default:
wx.showModal({
title: '错误',
content: res.errMsg,
showCancel: false,
})
wx.navigateBack({})
}
},
success: function (res) {
getCurrentPages()[1].setData(res.wifi)
}
})
//getCurrentPages()[1].setData({ SSID: 'wojiushiwifi' })
},
chstate(res){
getCurrentPages()[1].setData({mystate:res})
},
bind() {
//循环查询是否有设备成功绑定本账号,可换成websocket
let num=8
let hand=setInterval(function(){
if(num==0){
clearInterval(hand)
getCurrentPages()[1].chstate('联网失败')
return
}
num--
wx.request({
url: getApp().globalData.serveraddr +'/onLogin',
data: {
//devname: getCurrentPages()[1].data.devname,
openid: getCurrentPages()[0].data.openid
},
method: 'POST',
success: function (res) {
if (res.data.state == 2) {
clearInterval(hand)
wx.showToast({
title: '联网成功',
duration:3000
})
wx.navigateBack({
})
getCurrentPages()[0].onLoad()
return
}
},
fail: function (res) {
//num++
}
})
},3000)
},
ConnectEsp() {//连接设备并发送wifi name psk 本账号信息
getCurrentPages()[1].chstate('正在连接')
//连接设备AP
wx.connectWifi({
SSID: getCurrentPages()[1].data.ESPNAME,
password: getCurrentPages()[1].data.ESPPSK,
success: function (res) {
getCurrentPages()[1].chstate('正在发送密码')
const udp = wx.createUDPSocket()
const port = udp.bind(8888)
udp.onMessage(function (res) {
var str = String.fromCharCode.apply(null, new Uint8Array(res.message));
console.log('收到消息2:', str)
var js = JSON.parse(str)
console.log(res, js)
if (js.xrcode == 1) {
udp.close()
getCurrentPages()[1].chstate('云控成功收到密码,正在等待云控联网')
//连接回原本网络
wx.connectWifi({
SSID: getCurrentPages()[1].data.SSID,
password: getCurrentPages()[1].data.password,
fail: function (res) {
console.log(res)
getCurrentPages()[1].chstate('请手动联网')
},
})
//延迟13s后开始查询是否绑定成功
setTimeout(getCurrentPages()[1].bind, 13000)
}
})
//循环发送信息到设备
for (var i = 1; i <= 10; i++) {
setTimeout(function () {
var thisp = getCurrentPages()[1].data
udp.send({
address: '255.255.255.255',
port: 9527,
message: '^@^' + thisp.SSID + '^_^' + thisp.password + '^,^' + getCurrentPages()[0].data.openid + '^.^'
})//可加入CRC32校验
}
, i * 2000)
}
},
fail: function (res) {
console.log(res)
getCurrentPages()[1].chstate("无法连接到云控,可手动连接xryunkong 密码123404321")
}
})
},
写在最后
由于小程序和硬件部分主要由demo修改而来,其他代码也接近demo或者比较普通,所以贴的代码较少,而服务器端则内容比较多则只贴入口函数,由于时间和经验不足,所以有很多地方都没有完善,例如可以引入websocket改进 小程序的 配网、更新消息, 情景模式的加入 等等。
最后感谢腾讯云举办方提供开发板、云服务以及技术支持等等,使我学习到了很多前沿的技术,感谢!