最近天气很热,我租的房子又没有空调,基本上风扇一开就是一晚上,结果经常起床后发现口干舌燥的。我觉得这肯定是因为整晚吹风扇搞的,不管是不是,反正我觉得就是了。不开风扇吧,热!开风扇吧,早上起来不舒服,怎么办呢?能不能让风扇吹一会停一会这样的吹呢?让手机来当遥控器来控制风扇?加上语音控制?我看了下我那吃灰半年多的树莓派,觉得应该让它动一动了。
硬件准备
首先,电扇是必须的,树莓派吃灰了半年,也该工作工作了。其他再需要啥的就该淘宝了。树莓派控制电扇嘛,3v-7v直流信号控制220v交流的电磁继电器得一个。连接树莓派和继电器的杜邦线若干,连接电风扇和继电器得卡口一对,注意需要能承受住风扇的电流的,不要太细的。其他的以后再说吧!
东西准备好后,先连接电路。PO上几张图,不用好看,只要能说明问题。
拆掉底座后,图是这样的,左边是定时的,坏了定不了。右边是调速的。把调速的电源线断开,接到继电器得被控端。使用继电器的常闭触点,就是把树莓派拿掉,风扇和原来一样用。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
从代码看也比较简单,使用GPIO05来控制风扇,默认设置为低电平。低电平的时候,继电器常闭,风扇的开关打开,风扇就转。高电平的时候,继电器的常闭触点断开,风扇开关打开的时候,风扇也不转了。输入open的时候,05脚设置为低电平,风扇打开。输入close的时候,05脚设置高电平,风扇停止。
实现定时功能
做这个的初衷就是让风扇定时开一会儿关一会儿,所以能控制风扇后,第一时间自然就是把这个功能给实现掉了。用python来实现,代码自然简单的不能再简单了:
from SmartServer import SmartServer
import threading
import RPi.GPIO as GPIOfanState=[1]
def gpioInit():GPIO.setmode(GPIO.BCM)GPIO.setup(5,GPIO.OUT,initial=GPIO.HIGH)
def switchFan(open=[0]):fanState[0]= 1 if not open[0] else 0GPIO.output(5,fanState[0])print('fan close' if fanState[0] else 'fan open')
def timerCheck(time=60*15,func=0,args=()):if func:func(args)timer=threading.Timer(time,timerCheck,(time,func,args))timer.start()if __name__ == "__main__":gpioInit()timerCheck(time=60*15,func=switchFan,args=(fanState))print('now fanState :',fanState)order=''while order!='exit':order=input('input you order:')GPIO.cleanup()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
时间校准及离家模式
白天我不在家,风扇得保证不开,那首先得保证树莓派的时间正确。设置下定时更新网络上的时间,时区也得保证是我所用的时区,上海时区。
apt-get install ntpdate
sudo tzselect
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
sudo ntpdate ntp.ubuntu.com
vi /etc/crontab
然后我一般是7:30离家上班去,9:30左右回到家,所以在这个区间,如果我忘记关风扇了,它也应该保证不开,只要判断这个时间段就OK了:
def isLeave(leave=[7,30],back=[21,30]):now=time.localtime(time.time())return not (((now.tm_hour==leave[0] and now.tm_min>=leave[1]) ornow.tm_hour>leave[0]) and ((now.tm_hour==back[0] and now.tm_min1]) ornow.tm_hour0]))
编写服务器端接受指令
运行程序后,风扇就一直按照既定的模式运行,开一会关一会好像也不太好,比如今天天气比较热,我回来的比较早,我想它先扇两个小时,然后再开半个小时,关十五分钟,这样循环怎么办?嗯,还是得有个遥控器,反正树莓派联了家里的网,一回来手机也是要联上Wifi,那就直接用手机来做遥控器了。
想要手机控制树莓派,在树莓派上运行一个服务来接受来自手机的指令肯定是少不了的。一事不劳二主,依旧Python了。先写一个SmartServer的类,来创建服务器的Socket,来接受客服端的连接以及连接后的指令。
from socket import *
from time import ctime
from time import localtime
import threading
import time
import datetimeclass SmartServer(object):"""docstring for SmartServer"""HOST=''PORT=1122 BUFSIZ=1024 SERVERFLAG=TruefanSwitchTimestamp=0 fanState=0 fanForceClose=True def __init__(self):super(SmartServer, self).__init__()def clientSocketDoWhat(self,client,address,clientId):while True:try:data=client.recv(self.BUFSIZ)except Exception as e:print('error when recv data from client!')breakif not data:breakISOTIMEFORMAT='%Y-%m-%d %X'stime=time.strftime(ISOTIMEFORMAT, localtime())s='getIt %d , %s' %(clientId,data.decode('utf8'))client.send(s.encode('utf8'))print([clientId],[stime], ':', data.decode('utf8'))quit=(data.decode('utf8').upper()=="QUIT")if quit:breakclient.close()def openServer(self,host='',port=1122,bufsize=1024):self.HOST=hostself.PORT=portself.BUFSIZ=bufsizeADDR=(self.HOST, self.PORT)sock=socket(AF_INET, SOCK_STREAM)sock.bind(ADDR)sock.listen(5)print('Server start')while self.SERVERFLAG:print('等待接入,侦听端口:%d' % (self.PORT))tcpClientSock, addr=sock.accept()print('接受连接,客户端地址:',addr)clientId=int(time.time());print('分派给客户端的ID:%d'%(clientId))th=threading.Thread(target=SmartServer.clientSocketDoWhat,args=(self,tcpClientSock,addr,clientId,))th.setDaemon(True)th.start()sock.close()
if __name__ == "__main__":server=SmartServer()serverThread=threading.Thread(target=server.openServer,args=())serverThread.setDaemon(True)serverThread.start()print('server thread start')serverThread.join()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
写好后要确保它能正常工作,所以再写个客服端client.py:
from socket import *class TcpClient:HOST='192.168.1.102'PORT=1122 BUFSIZ=1024ADDR=(HOST, PORT)def __init__(self):self.client=socket(AF_INET, SOCK_STREAM)self.client.connect(self.ADDR)while True:data=input('>')if not data:breakself.client.send(data.encode('utf8'))print('发送信息到%s:%s' %(self.HOST,data))if data.upper()=="QUIT":break data=self.client.recv(self.BUFSIZ)if not data:print('no info from server, exit!')breakprint('从%s收到信息:%s' %(self.HOST,data.decode('utf8')))if __name__ == '__main__':client=TcpClient()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
在树莓派上运行服务器,然后在另外一台电脑上运行客服端,运行结果如下。这样树莓派就可以接受来自局域网的控制指令了。
Android端遥控器
树莓派的服务器端已经准备好了,并且测试通过了,那下一步就是来做个客户端。其实也看的出来,Android端的代码会比较简单,写个Socket连接上树莓派,发送指令基本就OK了。主要代码如下:
public class SmartClient implements Runnable{private UserConfig cOnfig=UserConfig.getInstance();private boolean socketFlag=false;private Gson gson;private String charSet="utf-8";private byte[] dataFromServer=new byte[2048];private Thread mThread;private Socket mSocket;private LinkedBlockingQueue commands;public SmartClient(){gson=new GsonBuilder().create();commands=new LinkedBlockingQueue<>();mThread=new Thread(this);}public void connectServer(){socketFlag=true;mThread.start();}private void connectToServer() throws IOException {Log.e("wuwang","try connect to socket"+config.getIp()+":"+config.getPort());mSocket=new Socket();mSocket.connect(new InetSocketAddress(config.getIp(),config.getPort()));Log.e("wuwang","try connect to socket");while (!mSocket.isClosed()&&socketFlag){OutputStream stream=mSocket.getOutputStream();try {Command command=commands.poll(20, TimeUnit.SECONDS);if(command==null) {stream.write("{}".getBytes(charSet));}else{stream.write(gson.toJson(command).getBytes(charSet));}} catch (InterruptedException e) {e.printStackTrace();continue;}InputStream in=mSocket.getInputStream();int result=in.read(dataFromServer);if(result>0){String value=new String(dataFromServer,0,result);Log.e("wuwang","dataFromServer::"+value);}}}public void addCommand(Command command){commands.offer(command);}public void addCommand(int type,int value){Command c=new Command();c.type=type;commands.offer(c);}@Overridepublic void run() {try {connectToServer();} catch (IOException e) {e.printStackTrace();}}public void close(){socketFlag=false;if(mSocket!=null){try {mSocket.close();} catch (IOException e) {e.printStackTrace();}}try {mThread.join();} catch (InterruptedException e) {e.printStackTrace();}}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
用Json来通信,来方便后面的扩展,用type来表示命令的类型,先做简单的,type为1表示强制打开风扇,type为0表示强制关闭。当然,控制端发送给树莓派后,树莓派需要解析出来,然后根据控制命令执行才行。增加解析这个type的来执行的代码到clientSocketDoWhat中,self.fanControl为测试gpio时的switchFan方法:
def clientSocketDoWhat(self,client,address,clientId):while self.SERVERFLAG:try:data=client.recv(self.BUFSIZ)except Exception as e:print(&#39;error when recv data from client!&#39;)breakif not data:breakISOTIMEFORMAT=&#39;%Y-%m-%d %X&#39;stime=time.strftime(ISOTIMEFORMAT, localtime())s=&#39;getIt %d , %s&#39; %(clientId,data.decode(&#39;utf8&#39;))client.send(s.encode(&#39;utf8&#39;))print([clientId],[stime], &#39;:&#39;, data.decode(&#39;utf8&#39;))command=json.loads(data.decode(&#39;utf8&#39;))type=command.get(&#39;type&#39;,-1)if type==1 :if self.fanControl:self.fanControl([1])elif type==0:if self.fanControl:self.fanControl([0])quit=(data.decode(&#39;utf8&#39;).upper()=="QUIT")if quit:breakclient.close()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
这样手机也可以遥控电扇开关了。至于其他的更为复杂的,用手机设置定时时间,也就大同小异了,发送json过去,树莓派解析,然后完成设置进行控制就OK了。
至此,风扇定时及android手机控制风扇的功能就OK了。