# 每个程序员心中都有一个智能家居的梦
[TOC]
上一篇提到用OpenWRT搞了点事情,然后由此就引发了一连串的效应,我突然想要利用OpenWRT搞点更有意义的事情了……
智能家居是最近比较火的概念……由此也涌现了好多平台……然而终有一天,大浪淘沙,最终屹立不倒才会坚持笑到最后……
在此期间,如果站错了队,日子可能不好过了……当然,当前来看,我不用担心这个问题,因为我当前不靠这个吃饭……
如果说预测一下的话,我比较看好的就是OpenWRT(前两天它跟LEDE项目合并了)和NodeMCU了……两个都是与Lua有着不解之缘的平台……而且这两个平台都是wifi平台正好相互照应……
说道这儿,还是插空说说我看好的Lua,我认为Lua语言是个很有潜力的语言,事实上并不是大家想象中的那么小众,不过是没有商业公司去吹捧它……
就当前来看,它横跨三大领域:游戏、Web、嵌入式(包括手机和智能家居),都是比较火的领域……除此之外,还有一些内存数据库用Lua……
因为Lua的良好嵌入特性,好多领域只要发展,大约都会跟Lua发生牵扯……不然大家可以几年之后再看我的这段话……
## 通过OpenWRT采集设备在线状态
既然想用OpenWRT搞点事情,那搞点啥好呢……一开始不要搞得太复杂……想来想去,搞个设备在线采集吧!
这样我就知道在某个时刻,那个设备在用我的路由器上网了……要是有邻居蹭网之类的,也能及时的察觉!
说干就干,按理说,我应该是写个lua脚本来搞事情的……但是我对OpenWRT的架构还真是不太熟……
到最后就用Shell脚本配合wget实现了一版采集并上传的在线设备MAC地址列表并上传的代码。
```Shell
#!/bin/sh
macs=$(iwinfo | awk '/SSID/{cmd="iwinfo "$1" assoclist";print("["$1"]");system(cmd)}' | egrep "[0-9A-Fa-f]{2}(\:[0-9A-Fa-f]{2}){5}" | awk '{printf $1"+"}')
tm=$(date +%Y%m%d%H%M%S)
key=YourKey
md5=$(echo -n ${tm}${macs}${key}| md5sum | awk '{print $1}')
wget -O - --post-data="macs=${macs}&time=${tm}&md5=${md5}" http://YourUploadURL.do
```
这段代码的主要功能就是用iwinfo命令获取接口列表,然后遍历每个接口相关联的设备列表,获得其中的MAC地址(通过正则匹配),然后用+连接起来。
获取时间,并通过将带时间、MAC地址列表、Key的数据获取MD5指纹,然后一起用POST方法发送给服务端。
我把这段代码保存在/root/collect/macs.sh,这个路径后面添加定时任务时用。
然后再通过crontab计划任务来定时执行这个脚本,这里,我们设置5分钟上传一次,因为太频繁了浪费资源,在线状态并不是一个频繁变化的量。
首先,运行`crontab -e`,添加下面一行保存:
```crontab
*/5 * * * * /root/collect/macs.sh &>/dev/null
```
最后一定记得添加一个空行,否则可能不执行,然后记得要打开crontab服务:
```Shell
# /etc/init.d/crontab enable
# /etc/init.d/crontab start
```
可以在服务端查看一下日志,是不是每隔五分钟就会请求一次。
这样,一个简单的采集端就完成了……剩下的就全是服务端的事了……
当然,服务端我用的还是Lua一家亲,OpenResty。到服务端这边,我就要考虑怎么存储了。我想来想去,用的MySQL,建表语句如下:
```SQL
CREATE TABLE IF NOT EXISTS `MAC_ADDR` (
`ID` int(10) unsigned NOT NULL auto_increment,
`ADDR` varchar(17) NOT NULL,
`NAME` varchar(50) default NULL,
PRIMARY KEY (`ID`),
UNIQUE KEY `UNIQUE_ADDR` (`ADDR`)
) COMMENT='MAC地址列表';
CREATE TABLE IF NOT EXISTS `MAC_DATA` (
`MACID` int(10) unsigned NOT NULL,
`WEB_TIME` timestamp NOT NULL,
`POST_TIME` timestamp NULL default NULL,
`VALUE` tinyint(4) NOT NULL,
PRIMARY KEY (`MACID`,`WEB_TIME`)
) COMMENT='保存MAC地址检测情况';
```
![MAC_ADDR表](MAC_ADDR.png "MAC_ADDR表")
![MAC_DATA表](MAC_DATA.png "MAC_DATA表")
服务端代码不算难,也不算太简单,有了表结构,大家一般就知道怎么写了:先根据传过来的MAC列表查询MAC_ADDR表,存在的,根据ID插入MAC_DATA,不存在的,先插入MAC_ADDR获得自动生成ID,再插入MAC_DATA……
一般情况下,MAC_DATA的VALUE都是1,没有0的情况,因为没有该MAC不会做插入操作,但是,为了保证即便是该路由器没有任何设备连接,MAC地址为空,也要知道这个时间点上传了数据,也就是说,要分清楚没有上传跟没有数据的区别,我在MAC_ADDR中添加了一条特殊的记录,ID=0,这条记录只要收到请求,就会直接插入MAC_DATA……
这样ID为0的记录保持了一个上传时间戳列表,该设备在该时刻到底有没有链接就一目了然了……此处代码不再贴过来了,对于搞web的人来说,设计完成后,实现都不是难事。
实现效果图如下:
![设备在线状况效果图](device.png "设备在线状况效果图")
然而,通过长时间观察发现,用这个来判断某时段某人在不在家并不好使……因为大半夜的时候手机经常会自己掉线……
唉,所以,太轻信这个是容易引发家庭矛盾的(经验之谈)……
## NodeMCU采集室内温湿度数据
上面那个采集设备在线情况的开发成功之后,大大增长了我的信心……我觉得再搞一个稍微有点实用性的……
于是我想起了我在一两年前买的那一堆电子元件……其中有各种传感器和MCU,包括51、STM8、STM32等……
当然,也顺便买了一个串口WIFI模块,ESP8266-01……
当时买它的原因仅仅是用来作为一个WIIF模块的,并不知道它自带一个MCU模块……毕竟它才只有十来块钱,而且还带wifi啊!
其实当时也是知道NodeMCU的,当时还是特别兴奋的,但是也并不知道我买的ESP8266-01也能刷NodeMCU……
事情发展到我刚搞完上面的设备在线状况采集的事情后不久,我在B站上看到了对于NodeMCU的讲解视频……
其中用的模块就是ESP8266-01……当时我就激动了!我都买了它一年了,不知道它还能这么玩?!赶紧搞起啊!
我参考的是这个链接:[ESP8266刷AT固件与nodemcu固件](https://www.cnblogs.com/yangfengwu/p/6247048.html "ESP8266刷AT固件与nodemcu固件")
然而我在[NodeMCU-Build](https://nodemcu-build.com/ "NodeMCU-Build")网站自己选择模块编译的固件,刷进去之后全是乱码,模块上的小灯不停的闪……
感觉并不能正常工作……修改了刷入频率也并不成功……但是采用最新版本的[ESP8266固件刷入工具ESP8266Flasher](https://pan.baidu.com/s/1qZEWQJi "ESP8266固件刷入工具ESP8266Flasher")自带的NodeMCU就没有任何问题……
我也不知道为什么,搞来搞去自己放弃了,只能用能刷进去的这个了……但是好多我想用的模块也不能用了,只能自己写了……
首先,我参照上面的方式,我得实现一个MD5算法吧,于是我在github上搜,搜到MD5.js不错,于是我花了很长时间,把它从js翻译成lua了……
代码不在这儿上传了,感兴趣的看我[上传到github中的项目](https://github.com/yimengqiannian/md5.lua "yimengqiannian/md5.lua")吧……
然后我兴致冲冲的想要在NodeMCU上试一下……结果……失败了……内存不足……于是我又想了好多办法,包括切分文件,编译,最终倒是运行成功了([参见upload2NodeMCU文件夹](https://github.com/yimengqiannian/md5.lua/tree/master/upload2NodeMCU "upload2NodeMCU文件夹")),但是……只要稍微多写几行代码就内存不够了……
根本不能运行……于是……最终我放弃了用MD5的想法……
然后就是温湿度传感器,DHT11的读取……这个本来不难……但是,我搞了一晚上才成功……为啥?被局部变量和全局变量坑了……
话说,对于DHT11来说,它是一个对时间特别敏感的模块……如果执行时间过慢,往往取不到数据……
而我就恰好遭遇了这个问题,然而我找不到执行时间慢的原因……后来我想到了,lua访问局部变量比访问全局变量速度要快……我把gpio.write和gpio.read赋值给局部变量,然后果然就成功了……怪自己学艺不精……
之后,我把所有的模块实现了,然后再init.lua中逐个调用的时候才发现……内存不够……根本不可能执行得起来……
事后,删删减减,再加上编译,总算是能让小芯片顺利的完成一个上传的过程了……
大家可以对比一下,这是[我期望的模块化代码](https://github.com/yimengqiannian/UploadDHT11DataByNodeMCU/tree/master/expect "我期望的模块化代码"),这是[最终运行的代码](https://github.com/yimengqiannian/UploadDHT11DataByNodeMCU "最终运行的代码")……
看过之后就知道,我也是好心酸的,做了好多无用功……
然后,总结一下关于NodeMCU的经验,这都是血的教训啊!与大家共勉:
- ESP8266-01引出脚的IO 0对应GPIO编号是3,IO 2对应的GPIO编号是4
- 使用局部变量可以大幅度提高速度
- 直接用编译后的文件可以避免某些内存不足的情况,有些文件只有在RST加低电位脉冲重启后立刻执行编译命令才有可能编译成功,可以多试几次
- init.lua是启动时自动加载的文件,但是这个文件可以是编译后的文件,尽管扩展名是.lua
- 调试时尽量不要用init.lua调试,因为一旦有错误NodeMCU会不停的重启,运气好的时候能把init.lua删除,但是大部分时候只能重新刷机,浪费的很多时间……
- 如果刷机也不管用的话,在刷机的镜像后面多添加其他的镜像进行覆盖,开机成功后format一下
- 在init.lua中写代码时,尽量不要一启动就运行,这样会增加NodeMCU不停的重启还删不掉的概率,使用tmr.alarm进行延时执行,这样,即便在代码出错时,也有充分的时间删除文件
- 模块交互时稍微加点延时,避免电压不稳定造成的问题
- 嵌入式开发不像一般的编码,能省则省,过分的追求模块化的结果是内存不足……
就这样,采集端算是搞出来了:
![NodeMCU采集端封装前](org.jpg "NodeMCU采集端封装前")
后来我就想封装了一下,于是我就剪了一个盒子:
![盒子](box.png "盒子")
后来我觉得我剪的这个盒子不错,不如做收纳盒吧……于是我就随便把这个采集器封装了一下,于是它最终变成这样了:
![NodeMCU采集端封装后](boxed.jpg "NodeMCU采集端封装后")
注意,这个采集端是附带一个前台的……本来是给telnet用的,这样在出什么事的时候,可以连上去看看情况,后来我想了想,这个telnet也可以伪装成一个http服务啊……反正都是TCP……当然,这是个预留的小后门,存在一定的安全风险……
因为它带一个小服务,就不能在DHCP动态分配IP了……要给它在路由器里配置一条静态IP,顺便给它设置一个主机名……这样,我们访问时就直接可以用主机名访问了:
![路由器上配置主机名和静态IP](router.png "路由器上配置主机名和静态IP")
于是浏览器访问时,可以直接使用`http://wendu/`访问,而telnet时也可以直接`telnet wendu 80`来访问。
用浏览器访问NodeMCU采集端的80端口效果是这样的:
![用浏览器访问NodeMCU采集端的80端口](wendu.png "用浏览器访问NodeMCU采集端的80端口")
而用putty的telnet协议访问NodeMCU的80端口是这样的:
![用putty访问NodeMCU的80端口](telnet.png "用putty访问NodeMCU的80端口")
采集端完成了,剩下的就是服务端了……服务端跟上面一样,只是说一下数据结构,不再贴代码了……
还是MySQL,数据结构比上一个简单多了,只用了一个表:
```SQL
CREATE TABLE `HUMITURE` (
`WEB_TIME` TIMESTAMP NOT NULL,
`POST_TIME` BIGINT(20) NULL DEFAULT NULL,
`TEMPERATURE` DECIMAL(6,3) NULL DEFAULT NULL,
`HUMIDITY` DECIMAL(6,3) NULL DEFAULT NULL,
PRIMARY KEY (`WEB_TIME`)
);
```
![HUMITURE表](HUMITURE.png "HUMITURE表")
这个表就不用解释了,大家一眼就知道该怎么存怎么取了……
然后就是服务端完成后的效果图:
[室内温湿度采集效果图](temperature.png "室内温湿度采集效果图")
## 前台界面美化
话说,上面两个功能完成之后,后面还有一个活不能不干,那就是这两个功能是要实际应用的,是要给家里用的……家里妹纸对漂不漂亮可刁了……
也就是说,如果界面做的漂亮,即便没什么用,妹纸也会说:嗯,做得不错……如果做得丑了,那么,妹纸是永远不会去用的,即便有用……
于是,我就在此基础上,奋斗了几个晚上,终于搞出了一个漂亮的登录页面,以及凑合点的展示页面……
登录页面(PC端)
![登录页面(PC端)](login.png "登录页面(PC端)")
欢迎页面(PC端)
![欢迎页面(PC端)](index.png "欢迎页面(PC端)")
温湿度展示页面(PC端)
![温湿度展示页面(PC端)](temperatureWeb.png "温湿度展示页面(PC端)")
设备在线状况展示页面(PC端)
![设备在线状况展示页面(PC端)](deviceWeb.png "设备在线状况展示页面(PC端)")
登录页面(手机端)
![登录页面(手机端)](loginPhone.png "登录页面(手机端)")
欢迎页面(手机端)
![欢迎页面(手机端)](indexPhone.png "欢迎页面(手机端)")
温湿度展示页面(手机端)
![温湿度展示页面(手机端)](temperaturePhone.png "温湿度展示页面(手机端)")
设备在线状况展示页面(手机端)
![设备在线状况展示页面(手机端)](devicePhone.png "设备在线状况展示页面(手机端)")
好了,这次分享就到这儿了,希望能给大家一点启发~