利用python完成一个智能插座

这个项目的编程语言使用的都是python,但还是必须稍微懂点前端的知识,爱动手的同学按着本篇文章应该都能自己完成一个智能插座。整个项目代码我放在了GitHub上,有需要的可以下载https://github.com/grey27/smartsocket

  • 硬件清单:
    esp8266模块、继电器、降压模块、普通插座
  • 用到的技术:
    MQTT、micropython、flask
项目完整流程图
流程图 .png

按照流程总共拆分成三个部分,分别为mqtt、硬件和flask

MQTT(消息队列遥测传输)是ISO 标准(ISO/IEC PRF 20922)下基于发布/订阅范式的消息协议。它工作在 TCP/IP协议族上,是为硬件性能低下的远程设备以及网络状况糟糕的情况下而设计的发布/订阅型消息协议,为此,它需要一个消息中间件。

简单解释下mqtt的工作原理,消息中间件就是一个分发的服务器,每个mqtt的客户端可以向服务器订阅和发布主题,就如同订报纸一般订阅一个主题后每期的新报纸便会发送给你,而每个客户端也可以是报纸厂可以发布任意名字主题,在这个项目里我们需要部署一个消息中间件,和两个客户端,一个负责发布主题对接flask一个负责订阅主题对接硬件,这样便可以实现对插座的控制。

部署消息中间件

中间件最好是在云上部署,我是在阿里云centos上部署开源的mqtt消息中间件mosquitto。

  • 加入yum源
    在/etc/yum.repos.d/目录中新建一个mosquitto.repo文件,里面写入:
[home_oojah_mqtt]
  name=mqtt (CentOS_CentOS-7)
type=rpm-md
baseurl=http://download.opensuse.org/repositories/home:/oojah:/mqtt/CentOS_CentOS-7/
gpgcheck=1
gpgkey=http://download.opensuse.org/repositories/home:/oojah:/mqtt/CentOS_CentOS-7//repodata/repomd.xml.key
enabled=1
  • 开始安装
    yum search all mosquitto
    yum install mosquitto mosquitto-clients
  • 启动服务
    mosquitto -c /etc/mosquitto/mosquitto.conf -d
    这样便部署完了mqtt的消息中间件,mosquitto默认端口是1883
利用python模拟mqtt的发布/订阅主题
  • 安装paho-mqtt库
    pip install paho-mqtt

  • 订阅主题

import paho.mqtt.client as mqtt

def on_connect(client, userdata, flags, rc):
    print("Connected with result code "+str(rc))
    # 连接时订阅test主题
    client.subscribe("test")
    

def on_message(client, userdata, msg):
    print(msg.topic+" "+str(msg.payload))
    
    
client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message
client.connect("127.0.0.1", 1883, 60)
client.loop_forever()
  • 发布主题
import paho.mqtt.client as mqtt

def on_connect(client, userdata, flags, rc):
    print("Connected with result code "+str(rc))
    

def on_message(client, userdata, msg):
    print(msg.topic+" "+str(msg.payload))
    
    
client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message
client.connect("127.0.0.1", 1883, 60)
# 发布 test主题
client.publish("test", 'hello')

运行订阅主题后,运行发布主题就能在订阅主题的程序中看到消息了。

第二部分 硬件

先看看整个硬件线路图

实际线路.png

其实就是一个降压模块将220v的交流电转为3.3v的直流电为8266模块和继电器供电,最后通过继电器连接插座。如果懂硬件的同学自己可以买回模块来接下线就行了,如果嫌麻烦也可以直接网上买成品的模块,
模块.png
淘宝上挺多的自己找找吧。注意用电安全,接入220V一端的部分最好用电工胶布封死,通电时不要接触模块

esp8266

说到物联网基本上就绕不开这个芯片了,网上资料也很多,开发方式也有很多种,而我这项目中选择的就是micropython开发,micropython是由英国剑桥大学教授Damien George发明的,Damien花费六个月的时间开发了micropython。(膜拜大神)在ST公司的微控制器上实现了Python3的基本功能,拥有完善的解析器、编译器、虚拟机和类库等。现在在众多开发者的努力下已经移植到更多的硬件平台上了。
想要了解micropython和python的不同以及快速上手可以阅读官方的github文档

烧写固件
  • 在micropython的官网上下载最新的固件
  • 下载烧录软件uPyCraft
    uPyCraft是国人开发的一款micropython的IDE,虽然bug还有挺多的但勉强能用了。放上软件的码云地址https://gitee.com/dfrobot/upycraft/tree/master,不想登陆下载的也可以用我分享的百度云下载
    链接:https://pan.baidu.com/s/1V4rggW4eW3AvlZp2oXCT_A
    提取码:xpcy
    烧写时需要将GPIO0置低电平,再用usb转ttl将8266连上电脑后,打开uPyCraft选择清除flash烧录固件。
    烧录固件.png

烧录完成后将GPIO0悬空再接上电脑打开uPyCraft就能进行代码编写了,默认里面只有一个boot.py文件,不需要改动,直接新建一个mian.py,代码写在里面便可以执行了,这个uPyCraft莫名的bug比较多,比如我遇到过tab键和空格数目不对,但在uPyCraft里显示是一样长度,然后编译代码一直报错缩进错误,最好是在自己习惯的编辑器里敲代码最后复制到uPyCraft里,或者是使用另一款软件uPyLoader,这是基于python写的一个软件,在python3环境下需要pyqt5库和qt5库,除了中文兼容有点问题外这个软件我没遇见过什么bug。

编写代码

esp8266支持SmartConfig(微信叫airkiss),但是micropython没有实现这个功能,所以我们只能使用web网页手动配置。第一次配置智能插座需要8266发射一个wifi信号,用户连接后访问后台网页,在网页上将家里的wifi信息输入,永久存储。之后8266就能连上家里的wifi。连上网络后只需要一直监听MQTT主题就行了,通过接收的主题信息对插座进行通电和断电操作。
稍微介绍下micropython专有的两个库

  • machine
    这个库包含的都是硬件相关的一些方法,比如控制引脚,通信协议,RTC等硬件基础功能。
    我们项目里只需要用到控制引脚功能
    这是官方例子,相信大家看一眼就知道怎么使用了
from machine import Pin

p0 = Pin(0, Pin.OUT)    # create output pin on GPIO0
p0.on()                 # set pin to "on" (high) level
p0.off()                # set pin to "off" (low) level
p0.value(1)             # set pin to on/high

p2 = Pin(2, Pin.IN)     # create input pin on GPIO2
print(p2.value())       # get value, 0 or 1

p4 = Pin(4, Pin.IN, Pin.PULL_UP) # enable internal pull-up resistor
p5 = Pin(5, Pin.OUT, value=1) # set pin high on creation
  • network
    网络相关的库,用来控制8266的wifi等功能。
    两种模式分别为STA和AP。直接看官方例子吧
import network

wlan = network.WLAN(network.STA_IF) # create station interface
wlan.active(True)       # activate the interface
wlan.scan()             # scan for access points
wlan.isconnected()      # check if the station is connected to an AP
wlan.connect('essid', 'password') # connect to an AP
wlan.config('mac')      # get the interface's MAC adddress
wlan.ifconfig()         # get the interface's IP/netmask/gw/DNS addresses

ap = network.WLAN(network.AP_IF) # create access-point interface
ap.active(True)         # activate the interface
ap.config(essid='ESP-AP') # set the ESSID of the access point

因为我们还需要使用mqtt,在micropython的github官方仓库找到需要的库。直接把umqtt里的simple.py代码拷下来就行了,使用的例子也可以在上面看到。
为了方便调用我自己写了一个类库

from simple import MQTTClient
from machine import Pin
import utime

class MQTT():
    def __init__(self,id,host,topic,pin=2):
        self.host = host
        self.topic = topic
        self.id = id
        self.pin = Pin(pin,Pin.OUT)
        
    def loop(self):
        c = MQTTClient(self.id, self.host) #建立一个MQTT客户端,传入连接id号和主机
        c.set_callback(self.sub_cb) #设置回调函数
        c.connect() #建立连接
        c.subscribe(self.topic) #监控主题,接收控制命令,
        while True:
            c.check_msg()
            if utime.time() % 10 == 0:  #每10秒ping一次服务器,不然很快客户端就会掉线
                c.ping()


    def sub_cb(self,topic, msg):   #回调函数,收到服务器消息后会调用这个函数
        print(topic, msg)
        if msg == b'on':
          self.pin.value(1)
        if msg == b'off':
          self.pin.value(0)
          
if __name__ == '__main__':
    mqtt = MQTT('39.108.210.212',b'test',2)
    mqtt.loop()

因为官方提供的也是一个简单的mqtt客户端,没有自动断线重连的机制,所以再循环监听时需要定时ping通服务器,一定要定时,不然循环ping的话会造成信息阻塞。

接下来就是main.py了,直接上代码,之前铺垫了那么多再加上注释我觉得大家可以看懂了

import socket
import network
import ure
import time
import machine
import mqtt

# 填入你自己服务器的ip
HOST = '39.108.210.212'

# 提示跳转模板 三个空,分别是提示词,跳转页面,等待跳转时间
HINT_HTML = '''
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="UTF-8">
        <title></title>
      </head>
      <body>
      {}
      <script>window.setTimeout("window.location='{}'",{});</script>
      </body>
    </html>
'''


def ConnectWifi(request):
  # 取出wifi名字密码
  print(request)
  r = ure.search('ssid=(.*)&password=(.*) H',request)
  ssid = r.group(1)
  password = r.group(2)
  
  # 如果找到中文ssid进行译码
  try:
    r = ure.search('(%..)+', ssid)
    cn = r.group(0)
    cn_byte = cn.replace('%', r'\x')
    cn_byte = eval("b"+"\'"+cn_byte+"\'")
    ssid = ssid.replace(cn, cn_byte.decode())
  except:
    pass
    
  # 连接wifi
  sta = network.WLAN(network.STA_IF)
  sta.active(True)
  sta.disconnect()
  sta.connect(ssid,password)
  return ssid,password

# 获取配置页面  
def get_index_html(ap_list):
  aplist = []
  for a in ap_list:
    aplist.append(a[0].decode())
  with open('html.py') as fp:
    index_html = fp.read()
  for ssid in aplist:
    index_html = index_html.replace('ssid1',ssid,2)
  return index_html
  
# 打开sta模式
sta = network.WLAN(network.STA_IF)
sta.active(True)

# 尝试打开ssid文件获取之前配置的ssid
try:
  with open('ssid.py','r') as fp:
    ssid = fp.readline()
    password = fp.readline()
  sta.connect(ssid,password)
  time.sleep(5)
except:
  pass

# 如果可以联网即监听mqtt
if sta.isconnected():
  try:
    mqtt_c = mqtt.MQTT("mqtt_id",HOST,b'switch',2)
    mqtt_c.loop()
  except:
    pass
    # machine.reset()
# 否则打开ap模式让用户连接配置
else:
  ap_list = sta.scan()
  index_html = get_index_html(ap_list)
  ap = network.WLAN(network.AP_IF)
  ap.active(False)
  ap.active(True)
  ap.config(essid='智能插座',authmode=0)

# 设置webserver
addr = socket.getaddrinfo('192.168.4.1', 80)[0][-1]
s = socket.socket()
s.bind(addr)
s.listen(5)
# 循环监听

while True:
  # 获取request
  cl, addr = s.accept()
  request = cl.recv(1024).decode()
  get = request.split('\n')[0]
  # 屏蔽对favicon.ico的请求
  if 'favicon.ico' in get:
    cl.sendall('HTTP/1.1 404')
    cl.close()
    continue
  else:
    responseHeaders = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n"
    cl.send(responseHeaders)
    
  if 'ssid' in get:
    cl.sendall(HINT_HTML.format('<h1>连接中。。。</h1>','tips',10000))
    cl.close()
    ssid,password = ConnectWifi(request.split('\n')[0])
    continue
    
  if 'tips' in get:
    if sta.isconnected():
      with open('ssid.py','w') as fp:
        fp.write(ssid)
        fp.write('\n')
        fp.write(password)
      tips = '<h1>配置完成,本机ip:' + sta.ifconfig()[0] + ',设备即将重启</h1><br><h1>访问<a href="http://' + HOST + '">' + HOST + '</a>登陆后台控制</h1>'
      cl.sendall(HINT_HTML.format(tips,'index',3000))
      cl.close()
      time.sleep(3)
      ap.active(False)
      machine.reset()
    else:
      cl.sendall(HINT_HTML.format('<h1>密码错误</h1>','index',3000))
      cl.close()
    continue
      
  if ('index' in get) or (len(get)==15):
    cl.sendall(index_html)
    cl.close()

关于html.py 其实就是一个输入wifi名字密码的html页面,因为在烧录时只能选择py文件所以才取这个名字。大概就是长这样:


连接页面.png

源码我放在了我的github上大家可以去下载,如果有会前端的同学可以自己写一个更好看的页面。

第三部分 flask

Flask是一个使用python编写的轻量级 Web 应用框架。
我们需要将每次插座操作进行计入所以需要使用flask_sqlalchemy插件,定时开关插座任务使用flask_apscheduler插件.写一个简单的页面


主页面.png

然后再用js发起ajax请求,使后台发送mqtt消息最终达到控制插座的目的。
相关代码都在github上了,注意下载的源码所有的主机ip,mysql配置要根据自己的设置。

  • 最后展示一下硬件的完成品


    实物图.JPG
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容