# 每个程序员心中都有一个智能家居的梦 [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 "设备在线状况展示页面(手机端)") 好了,这次分享就到这儿了,希望能给大家一点启发~


发表评论

必填,公开,这样称呼起来方便~

必填,不会被公开,不必担心~

http://

非必填,公开,目的是方便增进友好访问~

必填,请输入下方图片中的字母或数字,以证明你是人类

看不清楚
必填,最好不要超过500个字符
     ↑返回顶端↑