赞
踩
更新前言
在使用了几个月后,根据自己的使用情况,发现了一些问题并完善了一下代码。更新的文字部分用蓝色标识出来了。代码则是直接覆盖了。
前言
由于出门不想带钥匙,因此想做一款自动开门的装置。而之前接触过Arduino,因此基于此开始制作装置,同时本文也记录了遇到的多次的问题和改进措施,最终实现效果较好,达到预期目标。
目录
提前上网看了很多案例,大多数是基于NFC的,少部分基于WiFi和蓝牙。最初我的想法就是做一个NFC的,由于其响应快,操作简单,而为了保障装置的安全性和隐蔽性,不能将装置牵线放在门外,而我们的门是厚铁防护门,因此也不能将NFC放在门后。所以做一种即快速又保障安全还操作简单的开门装置就成了一开始的难题了,如何选一种认证方式呢。
相比于蓝牙,WiFi用的也比较便捷,因此我便选定做基于WiFi的开门装置,但WiFi需要每次用到手机发送指令来实现开门,因此效率相比NFC会低一些,也算是一种妥协。
参考Arduino+esp8266-01+舵机 制作基于局域网的遥控门禁这篇文章,我便开始制作装置和写代码。但在做好上门的时候发现,这样一个舵机完全拉不动门把,试了试,我们这门把不仅用力大而且要转动很大幅度,无可奈何,我最后只好换成一个强力电机,拉力在7.2kg左右才勉强拉得动。在使用很久之后,发现这个门把手质量真好力很大,都拉断了几次绳子,甚至还把用热熔胶粘的电机给扯下来了。而且绳子会因为多次的拉动变形伸长甚至断裂,对误差归零会有一定影响。
在做好装置后,由于每次都需要打开软件TCP Client,输入ip和端口再连接,连接上了才能发送开门指令,还需要自己输入,那这样我还不如把钥匙放包里,每次拿出来再开门都比这快,还不会出bug。因此使用TCP Client开门显然是不现实的。而翻遍了软件市场,还没人做出基于TCP的通信软件,能够实现一键开门,因此还是只能自己来做。之前有过做app的经历,不过还是基于图形化的,而且如果不是Android,还得跑不同平台上来开发软件,那复杂程度就不是我能做的了。
我就想用微信小程序来开发,不仅不需要安装,而且适配所有手机,还可以深度定制,做到一键开门(输入ip和端口+连接+发送指令)。此外,由于安全性和网络连接问题,未考虑远程开锁,现已有软件可以通过远程控制一键开门,但是室内没有连上网的WiFi,因此也没法实现远程,如果有,可以省去微信小程序开发这一步。
此外,由于采用电源供电,因此功耗控制方面也需要解决,但是目前我并没有找到更好的方法,因此这部分之后还有很大的改进地方。功耗方面,我使用的是一个2200mAh的9V电池和20000mAh的充电宝,每天从早上9点到晚上10点,大概可以用个5天的样子。一般是充电宝先没电,wifi的功耗还是挺大的呀。
总结
以上就是整个制作的一个经历和思路了。本装置用于自动开门具有一定的安全性和便捷性,但不能忘记带钥匙在背包里防止被锁外面(如果装置没电)。后期如果想做远程开锁也可以基于此进行修改。整套装置成本大约在100元以内,耗时1周(加上一次性购买部件的时间),仅供参考。以下分别介绍每个部分的内容。
由于需要用到WiFi通信,因此需要用AT指令首先设置WiFi的名称和密码。
参考Arduino leonardo+esp8266-01作服务端与APP进行数据通信
ESP8266 WiFi模块AT指令_学习笔记可以实现以上内容,此处省略。
每次通信前,需设置通信模式和端口。
- Serial.begin(115200);
- delay(10);
- mySerial.begin(115200);
- delay(10);
- mySerial.print("AT+CIPMUX=1\r\n");
- delay(10);
- mySerial.print("AT+CIPSERVER=1,10500\r\n");
- delay(10);
这里,每次需停顿10ms是因为WiFi模块不能一次性接受全部指令。而通信端口为10500是自己设定的,大部分教程都默认是8080,而在微信小程序里这个端口是被禁止的,详见编程手册,因此这里也是一个坑。
在使用TCP客户端与Arduino通信时,由于通信的内容不止发送的内容,因此需对内容进行处理。如下图,前面的字符每次都是一样的,而红圈里的不一样,因此每次需从后面判读命令即可。
这里,我们需要Arduino在接受到指令后,做出响应。例如,当Arduino收到on时表示开门,通过指定电机的转动时间可以控制电机拉动手把,其中本文拉动的时间仅作为一个参考。当收到其余指令时表示错误命令不开门。因此只需判断Arduino收到的命令即可控制,无非只是一个字符比较的逻辑在里面,实现较简单。而电机的正反转需要借助L298N模块来实现。
而这里我又遇到两个bug。一个是在使用一段时间后,我发现由于电池电压降低,原本的拉动时间不够了,必须要长一点的,因此在发送指令时,通过在指令中加入拉动时间来手动控制,可以在遇到突然卡住和电压降低问题时紧急开门。例如发送on27,其中27表示2700ms。另一个则是在最初安装时,由于电机正转和反转的力不同,导致两边行程不一样。如果正转和反转的时间一样,每次都会回退一些,导致下次直接打不开,因此通过调试减少反转的时间来保证行程一致,但由于多次会积累误差,所以使用一段时间后需手动误差归零。在使用一段时间后,发现这个开门时间和时间差并不是固定的,为了方便在微信小程序使用,增加了这两个部分的读取,使其可以手动调整。
最终代码如下:
- void loop() {
- //定义一个整数型变量i
- int i;
- //如果串口收到有数据
- if (mySerial.available() > 0)
- {
- for (i = 0; i < 20; i++)
- {
- cmd[i] = '\0';
- }
-
- for (i = 0; i < 19; i++)
- {
- if (mySerial.available() > 0)
- {
- cmd[i] = mySerial.read();
- }
-
- else
- {
- break;
- }
- }
-
- for (i = 0; i < 10; i++)
- {
- cmd1[i] = cmd[i + 11];
- }
- valid_cmd = true;
- }
-
- //判断变量cmd的值,开始处理
- if (valid_cmd)
- {
- //如果变量cmd的前2位的值是on
- if (0 == strncmp(cmd1, "on", 2))
- {
-
- if (cmd1[2] != '\0') {
- //解算开门时间,需把字符变量变成数字型变量
- int mytime = (cmd1[2] - '0') * 1000 + (cmd1[3] - '0') * 100;
- //解算延时时间,需把字符变量变成数字型变量
- int ys = (cmd1[5] - '0') * 100 + (cmd1[6] - '0') * 10;
-
- digitalWrite(13, HIGH);
- digitalWrite(in1, HIGH);
- digitalWrite(in2, LOW);
- Serial.println("Open");
- delay(mytime);
- digitalWrite(in1, LOW);
- digitalWrite(in2, HIGH);
- delay(mytime - ys);
- digitalWrite(in1, LOW);
- digitalWrite(in2, LOW);
- digitalWrite(13, LOW);
- }
- else {
-
- digitalWrite(13, HIGH);
- digitalWrite(in1, HIGH);
- digitalWrite(in2, LOW);
- Serial.println("Open");
- delay(2700);
- digitalWrite(in1, LOW);
- digitalWrite(in2, HIGH);
- delay(2350);
- digitalWrite(in1, LOW);
- digitalWrite(in2, LOW);
- digitalWrite(13, LOW);
- }
- }
-
- else
- {
- Serial.println("wrong order");
- digitalWrite(13, LOW);
- }
- valid_cmd = false;
- }
- delay(100);
- }
通过分析一般TCP通信的过程发现,只需编写单向通信即手机端到WiFi模块端的通信即可,相比之下简单许多。对于这样的通信来说,只需提供ip、端口和通信内容即可。因此编写出如下的一个页面。这也是我第一次接触网页的编程,代码能跑就行。而在编写之前,还需要去注册微信小程序之类的,网上有很多教程,此处省略。
其中,每个框的内容默认就有,因此可以直接发送,而不需要填写内容,而当改变通信内容时,在框中输入新的即可。对于一般的发送过程为连接-发送-断开连接,而为了实现一键发送,把三部分综合起来即可实现。
- <view class="itemView1">IP地址:
- <input class="input" bindinput ="ipInput"/>
- </view>
- <view class="itemView">端口:
- <input class="input" type="number" bindinput="dkInput" />
- </view>
- <view class="itemView">通信内容:
- <input class="input" bindinput="wordInput" />
- </view>
- <view class="itemView">延时设定:
- <input class="input" bindinput="ysInput" />
- </view>
-
- <text>\n</text>
- <text class="body">------------------------------------</text>
-
- <text class="body">当前(默认)通信IP地址为:{{ip}}</text>
- <text class="body">当前(默认)通信端口为:{{dk}}</text>
- <text class="body">当前(默认)通信内容为:{{word}}</text>
- <text class="body">当前(默认)延时设定为:{{ys}}</text>
- <view class="viewName"></view>
-
- <button type="primary" size="default" hover-class="button-hover" class="button-voice-play" bindtap="loginBtnClick">连接服务器</button>
- <button type="primary" size="default" hover-class="button-hover" class="button-voice-play" bindtap="sendClick">发送</button>
- <button type="primary" size="default" hover-class="button-hover" class="button-voice-play" bindtap="offClick">断开连接</button>
- <button type="default" size="default" hover-class="button-hover" class="button-voice-play" bindtap="oneClick">一键发送</button>
-
- <text>\n</text>
- <text class="body">当前TCP通信状态:{{state}}</text>
- <text class="body">当前WIFI SSID:{{wifissid}}</text>
- /**index.wxss**/
- view{
- width:800rpx;
- height: 120rpx;
- font-size: large;
- }
- button {
- margin-top: 10rpx;
- margin-bottom: 10rpx;
- }
- .itemView{
- margin-left: 100rpx;
-
- }
- .input{
- border: 3rpx solid #04BE02;
- border-radius: 10rpx;
- margin-right: 300rpx;
-
- }
- .itemView1{
- margin-left: 100rpx;
- margin-top: 100rpx;
- }
-
- .body{
- font-size:medium;
- display: flex;
- flex-direction: column;
- align-items: center;
- }
1. 内容输入代码
- ipInput:function(e){
- this.setData({ip:e.detail.value})
- },
- dkInput:function(e){
- this.setData({dk:e.detail.value})
- },
- wordInput:function(e){
- this.setData({word:e.detail.value})
- },
- ysInput:function(e){
- this.setData({ys:e.detail.value})
- }
2. 发送代码
- loginBtnClick: function () {
- console.log("连接");
- tcp.connect({address: this.data.ip, port: this.data.dk})
-
- this.setData({
- state: '已连接'
- })
- },
- sendClick: function () {
- console.log("发送:"+this.data.word+' '+this.data.ys);
- tcp.write(this.data.word+' '+this.data.ys)
-
- this.setData({
- state: '已发送'
- })
- },
-
- oneClick: function () {
-
- console.log("连接1");
- tcp.connect({address: this.data.ip, port: this.data.dk})
- setTimeout(function () {
- console.log("发送1");}, 50)
- tcp.write(this.data.word+' '+this.data.ys)
-
- setTimeout(function () {
- console.log("关闭连接1");}, 100)
- tcp.close()
-
- setTimeout(function () {
- console.log("连接2");}, 200)
- tcp.connect({address: this.data.ip, port: this.data.dk})
- setTimeout(function () {
- console.log("发送2");}, 250)
- tcp.write(this.data.word+' '+this.data.ys)
- setTimeout(function () {
- console.log("关闭连接2");}, 300)
- tcp.close()
- this.setData({
- state: '已关闭'
- })
- },
- offClick:function () {
- console.log("断开");
- tcp.close()
- this.setData({
- state: '已关闭'
- })
- }
到此就能实现全部功能了。
在组装好上述装置后,可以用热熔胶粘在门后,最后调试,最终的成图如下。
操作逻辑:
在手机端打开微信小程序后,连接这装置的WiFi后,点击一键发送即可完成开门操作。
!!!!!!!!!!!!!!!!(非常重要)!!!!!!!!!!!!!!!!!!!!!
注意,在上传时需要拔掉单片机引脚的2,3,10,11连线,否则将可能导致上传失败,最安全的办法是什么都不连先下载后再连线,此问题由某位同学复现时发现的。
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Arduino的代码如下:
- #include<SoftwareSerial.h>
- #include<string.h>
- SoftwareSerial mySerial(10, 11);
-
- const int in1 = 2;
- const int in2 = 3;
- char cmd[20];
- char cmd1[10];
- bool valid_cmd = false;
-
- void setup() {
- Serial.begin(115200);
- delay(10);
- mySerial.begin(115200);
- delay(10);
- mySerial.print("AT+CIPMUX=1\r\n");
- delay(10);
- mySerial.print("AT+CIPSERVER=1,10500\r\n");
- delay(10);
- pinMode(13, OUTPUT);
- pinMode(in1, OUTPUT);
- pinMode(in2, OUTPUT);
- digitalWrite(in1, LOW);
- digitalWrite(in2, LOW);
- }
-
- void loop() {
- int i;
- //如果串口收到有数据
- if (mySerial.available() > 0)
- {
- for (i = 0; i < 20; i++)
- {
- cmd[i] = '\0';
- }
-
- for (i = 0; i < 19; i++)
- {
- if (mySerial.available() > 0)
- {
- cmd[i] = mySerial.read();
- }
-
- else
- {
- break;
- }
- }
-
- for (i = 0; i < 10; i++)
- {
- cmd1[i] = cmd[i + 11];
- }
-
- valid_cmd = true;
- }
-
- //判断变量cmd的值,开始处理
- if (valid_cmd)
- {
- //如果变量cmd的前2位的值是on
- if (0 == strncmp(cmd1, "on", 2))
- {
-
- if (cmd1[2] != '\0') {
- int mytime = (cmd1[2] - '0') * 1000 + (cmd1[3] - '0') * 100;
- Serial.println(mytime);
- int ys = (cmd1[5] - '0') * 100 + (cmd1[6] - '0') * 10;
- Serial.println(ys);
-
- digitalWrite(13, HIGH);
- digitalWrite(in1, HIGH);
- digitalWrite(in2, LOW);
- Serial.println("Open");
- delay(mytime);
- digitalWrite(in1, LOW);
- digitalWrite(in2, HIGH);
- delay(mytime - ys);
- digitalWrite(in1, LOW);
- digitalWrite(in2, LOW);
- digitalWrite(13, LOW);
- }
- else {
-
- digitalWrite(13, HIGH);
- digitalWrite(in1, HIGH);
- digitalWrite(in2, LOW);
- Serial.println("Open");
- delay(2700);
- digitalWrite(in1, LOW);
- digitalWrite(in2, HIGH);
- delay(2350);
- digitalWrite(in1, LOW);
- digitalWrite(in2, LOW);
- digitalWrite(13, LOW);
- }
- }
-
- else
- {
- Serial.println("wrong order");
- digitalWrite(13, LOW);
- }
- valid_cmd = false;
- }
- delay(100);
- }
微信小程序的标签和样式完整代码前面已有,此处只写逻辑完整代码部分:
- const tcp = wx.createTCPSocket()
- var app = getApp()
- Page({
- /**
- * 页面的初始数据
- */
- data: {
- ip:'192.168.4.1',
- dk:'10500',
- word:'on37',
- ys:'40',
- state:'未连接',
- wifissid: '' // WIFI ssid
- },
-
- onLoad: function (e) { // 获取参数
- var that = this;
- wx.startWifi({
- success (res) {
- console.log(res.errMsg)
- }
- })
- wx.getConnectedWifi({
- success: function(e){
- that.setData({
- wifissid: e.wifi.SSID,
- })
- console.log("wifiyes");
- },
- fail:function(e){
- console.log("wififail"+JSON.stringify(e));
- }
-
- })
- },
-
- ipInput:function(e){
- this.setData({ip:e.detail.value})
- },
- dkInput:function(e){
- this.setData({dk:e.detail.value})
- },
- wordInput:function(e){
- this.setData({word:e.detail.value})
- },
- ysInput:function(e){
- this.setData({ys:e.detail.value})
- },
- loginBtnClick: function () {
- console.log("连接");
- tcp.connect({address: this.data.ip, port: this.data.dk})
-
- this.setData({
- state: '已连接'
- })
- },
- sendClick: function () {
- console.log("发送:"+this.data.word+' '+this.data.ys);
- tcp.write(this.data.word+' '+this.data.ys)
-
- this.setData({
- state: '已发送'
- })
- },
-
- oneClick: function () {
-
- console.log("连接1");
- tcp.connect({address: this.data.ip, port: this.data.dk})
- setTimeout(function () {
- console.log("发送1");}, 50)
- tcp.write(this.data.word+' '+this.data.ys)
-
- setTimeout(function () {
- console.log("关闭连接1");}, 100)
- tcp.close()
-
- setTimeout(function () {
- console.log("连接2");}, 200)
- tcp.connect({address: this.data.ip, port: this.data.dk})
- setTimeout(function () {
- console.log("发送2");}, 250)
- tcp.write(this.data.word+' '+this.data.ys)
- setTimeout(function () {
- console.log("关闭连接2");}, 300)
- tcp.close()
- this.setData({
- state: '已关闭'
- })
- },
- offClick:function () {
- console.log("断开");
- tcp.close()
- this.setData({
- state: '已关闭'
- })
- }
-
- })
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。