SkillAgentSearch skills...

Smartlife

a small smart light project

Install / Use

/learn @tagword/Smartlife
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Nodemcu、无线射频和Wifi实现家庭灯光控制

之前参与智能音箱的项目,做语义理解,但硬件方面,实在是小白,这段时间腾出些时间,折腾下硬件,把家中的灯光整体改造一下。这篇文章从设计、硬件、软件、云服务、手机应用5个方面记录了这次改造中所获取的信息以及相关的程序代码。最终构建了一个通过手机控制和传统控制兼容的智能灯光。

<a href="images/screen.jpg" target="_blank"><img src="images/screen.jpg" width="240" border="10" /></a>

系统设计

房屋状况

  • 单火线, 覆盖屋内大部分的开关,是开放商交付时的布线方式。
  • 零火线,两组射灯和电视墙灯带(有两组顶灯的单火开关和射灯开关放在一起)。

有线和无线

智能家居分有线和无线两类,有线稳定,但需装修前布线,工程量大;无线稳定稍差,但灵活便于安装。DIY的话,无线方案的资源多,结合自家房屋状况,无线的方案是首选。

Wi-Fi、射频、ZigBee和其他

无线控制技术,以Wi-Fi、射频ZigBeeZ-Wave为主,它们各有优劣,可从功耗、效率、安全、集控、分控等多方面对比,这里主要说说Wi-Fi和射频两个方案。

  • Wi-Fi控制,是给开关添加Wi-Fi开关模块,让开关联网从云端获取指令,操作继电器控制开关的方案。任意一个设备都是单独产品。目前能看到大量的智能产品,里面都有类似的模块。

  • 射频,是通过无线射频传递指令,通过给开关添加射频接收模块,接收来自发射模块的指令,来控制开关的方案,类似车子遥控。射频信号以433MHz和315MHz为主,产品成熟,接收端一般是学习型模块,发射端也采用通用的芯片模块。若要实现手机控制,需添加一个主机。

单火线电路和零火线电路

产品按电路可分零火线开关单火线开关两种,前者稳定,后者目前还存在技术问题。智能模块本身也是用电器,需要保持运行来获取控制信号,目前单火线开关的技术是维持小电流让模块运行,却又达不到让电灯发光的功率,但如果电灯功率低于理论值3W,就会出现灯光闪烁的问题。目前了解到市面上已有单火电源模块:PI-3v3-B4

云服务

我国网络运营商都不提供独立IP地址,IOT设备从云服务器获取控制指令是当前的主流方式。云服务的载体,可以是任意一台云服务器,也可以选择IOT的云服务平台。我是在自有服务器上搭建云服务。云服务的技术,采用RESTful和一个静态网页。操作流程为手机应用发送控制指令到云服务,云服务后台处理并更新静态网页,智能模块以轮询的方式获取控制指令,并控制电灯开关。

手机应用

IOS下的应用,使用Xcode,做个能实现HTTP POST的简单程序就好。

最终系统方案

因为单火线的限制和单火线开关闪烁的问题,最终方案是Wi-Fi和射频联合使用的方案,并且实现手机控制。把目标分解一下就成了采购相关物料,来完成下面几个任务:

  1. 制作“可联网射频发射主机”
  2. 制作”Wi-Fi开关“
  3. 制作“射频开关“
  4. 建立“云服务”
  5. 制作手机应用

物料

NodeMCU开发版

网上查了查资料是性价很高的板子,购买价格不到20元,基于ESP8266-12E Wi-Fi芯片。有详尽的资料NodeMCU Documentation,友好的自定义固件生成服务NodeMCU-Build,固件烧入程序NodeMCU PyFlasher,开发工具ESPlorer, 支持编程语言eLuaMicroPython,板上的10个GPIO接口,基本满足任何开发的需求,最后,USB供电兼调试接口,让我这个初学者也十分容易上手。

<a href="images/NODEMCU-ESP8266-LUA-CP2102-WIFI.jpg" target="_blank"><img src="images/NODEMCU-ESP8266-LUA-CP2102-WIFI.jpg" alt="NodeMCU" width="480" border="10" /></a>

单火线开关模块

在找射频产品的时候看到这个产品,直接解决单火线的问题,价格30元不到,接线简单,可以接收并学习常用的射频指令,如PT2262,这个模块还有一个优点是有三种模式:

  1. 开/关,关闭状态下按一下开,打开状态下再按一下关。
  2. 总开,开关总是打开,如果原来是打开状态,保持状态不变
  3. 总关,开关总是关闭,如果原来是关闭状态,保持状态不变

可以实现灯光开关单控制,也可是实现模式控制,如“观影模式”、“会客模式”。另外,每个模式可以接收4个指令,也就是说,一盏灯可以通过最多12个指令实现不同的控制。这种设置也给我制作“Wi-Fi开关模块”缕清了思路。

<a href="images/rf-switch.jpg" target="_blank"><img src="images/rf-switch.jpg" alt="rf-switch" width="240" border="10" /></a> <a href="images/rf-switch-connect.png" target="_blank"><img src="images/rf-switch-connect.png" alt="rf-switch-connect" width="240" border="10" /></a>

射频发射模块

对射频发射器我并不熟悉,只是根据PT2262的协议和433MHz随意选择了一个,担心信号不稳定,所以看着这个天线这么长,而且宣传资料说能到几千米(我家不是体育馆^^),就买了这个。

<a href="images/rf-emitter-receiver.png" target="_blank"><img src="images/rf-emitter-receiver.png" alt="rf-emitter-receiver" width="240" border="10" /></a>

四路继电器

对继电器,和发射模块一样都属于硬件范畴,我并不熟悉,由于看过NodeMCU的资料,它能提供给继电器的最大电压是3.3V,所以就根据这个以及需要连接的继电器选择了这个4路的3.3V的继电器,产品宣传还提到了“带光耦隔离”,貌似在工作上比不带的更加稳定。

<a href="#" target="_blank"><img src="images/relay-4ways.png" alt="relay-4ways.png" width="240" border="10" /></a>

AC-DC降压模块-220v转5v,AC-DC降压模块-220v转3.3v

NodeMCU的连接USB的工作电压是5V,但是我好想一份资料里面看到过工作点在3.3v到12v之间。因为我在这次改造中用了两个NodeMCU,其中一个连接继电器埋到了开关里面,所以需要用到这个小模块,不然太占空间。但是已经埋进去了,废了很大的劲,忘记里面用的是5v的还是3.3v的降压模块,下次再拆出来看看。另外一个NodeMCU直接USB连接。

其他

  • 杜邦线若干
  • 云服务器一台,直接用了自己目前使用的服务器。
  • 智能手机一台,废话了。

硬件部分

可联网射频发射主机

这个设备用到两个模块NodeMCU开发板以及射频发射模块,连接实物图如下,分别将NodeMCU的接口和射频模块上连接:

| NodeMCU | 射频模块 | 说明 | | ------- | -------- | --------------------------------- | | D1 | D0 | 数据接口 | | D2 | D1 | 数据接口 | | D3 | D2 | 数据接口 | | D4 | D3 | 数据接口 | | 3V3 | +9V | 正极, 3V3也能够使这个射频模块工作 | | GND | GND | 负极 |

<a href="images/rf_sender.jpg" target="_blank"><img src="images/rf_sender.jpg" alt="rf-sender" width="480" border="10" /></a>

Wi-Fi开关

这个模块已经埋如入墙内,翻出来太麻烦,这里拿一个三路继电器做演示,四路连接的方法是一样的,不复杂。NodeMCU接口要按照下面表格,不然得在后面的代码部分做修改。

| NodeMCU | 射频模块 | 说明 | | ------- | -------- | -------- | | D5 | CH1 | 数据接口 | | D6 | CH2 | 数据接口 | | D7 | CH3 | 数据接口 | | D8 | CH4 | 数据接口 | | 3V3 | VCC | 正极 | | GND | GND | 负极 |

<a href="images/relay-connect.jpg" target="_blank"><img src="images/relay-connect.jpg" alt="relay-connect" width="480" border="10" /></a>

射频开关

采购物料的时候,直接购买了单火线开关模块,这里直接跳过。

软件部分

该部分以代码为主,开发板烧入,云服务器的租用,手机应用开发环境的搭建皆不在本文内容中。

固件制作

<a href="images/nodemcubuild.png" target="_blank"><img src="images/nodemcubuild.png" alt="relay-connect" width="480" border="10" /></a>

进入NodeMCU-Build,输入邮箱地址,再选择下面的模块,在最下方点击Start your build,不用多久邮箱就会说到两封邮件,一封是说正在制作请稍后,另一封就制作好的,下载即可烧入。

固件模块: cron,crypto,dht,encoder,enduser_setup,file,gpio,http,mqtt,net,node,sjson,tmr,uart,wifi

控制码规格

射频发射模块有四个数据接口,每次给接口一个高电平,就发射一次信号,而且不同的高电平组合所发射信号不同,所以一共可以组成16组不同的数据信号,这类似于二进制的信号结构,见表格:

| 指令 | 二进制 | 十进制 | 备注 | | ------ | ------ | ------ | ------------- | | 指令0 | 0000 | 0 | 该指令不工作 | | 指令1 | 0001 | 1 | | | 指令2 | 0010 | 2 | | | 指令3 | 0011 | 3 | | | 指令4 | 0100 | 4 | | | 指令5 | 0101 | 5 | | | 指令6 | 0110 | 6 | | | 指令7 | 0111 | 7 | | | 指令8 | 1000 | 8 | | | 指令9 | 1001 | 9 | | | 指令10 | 1010 | 10 | | | 指令11 | 1011 | 11 | | | 指令12 | 1100 | 12 | | | 指令13 | 1101 | 13 | | | 指令14 | 1110 | 14 | | | 指令15 | 1111 | 15 | |

Wi-Fi开关也不复杂,根据四路继电器控制四路灯光,设置4位的信号,每一位对一路继电器即可。

| | 开 | 关 | | ------------ | --- | --- | | 第一路继电器 | 1 | 0 | | 第二路继电器 | 1 | 0 | | 第三路继电器 | 1 | 0 | | 第四路继电器 | 1 | 0 |

开发版和云服务交互获的指令格式,JSON格式,"rf"表示射频指令,"wifi"表示Wi-Fi指令,4位的数字表示控制信号,而另一个数字,如下面的“1513249981”,表示指令发送的时间戳。

{
    "rf": [["0100","0011"], 1513249981], 
    "wifi": ["0000", 1513278152]
}

射频主机和Wi-Fi开关的代码

注意,中文的注释在程序写入后会引起错误,所以下面代码需要去处中文注释才能正常使用

init.lua

系统初始化文件,每次NodeMCU重启后,会执行该文件该文件一共包含了两个模块:

-- init.lua
connect = require("connect")
connect.start()
light = require("light")
light.start()

connect.lua

Wi-Fi连接模块,固件中的end_user, 用户可以通过连接开启的热点网络输入个人Wi-Fi的账号密码,使设备连网。

-- file: connect.lua
local module = {}
function module.start()  
enduser_setup.start(
    function() -- 连接成功打印当前IP地址
        print("Connected to wifi as: " .. wifi.sta.getip())
    end,
    function(err, str) -- 连接不成功打印错误信息
        print("enduser_setup: Err #" .. err .. ": " .. str)
    end
);
end
return module  

light.lua

灯光的总控模块,代码集合了射频和Wi-Fi控制,在我的工程中,两个NodeMCU分别使用了其中的一种控制方式。按本文的介绍,需要将变量"url"里面的"服务器地址"修改为对应的内容。

-- file: light.lua
local module = {}
function module.start()  

-- 设置接口,按照硬件部分的连接方式
--[[
D0 = 1
D1 = 2
D2 = 3
D3 = 4
CH_1 = 5
CH_2 = 6
CH_3 = 7
CH_4 = 8
]]
gpio_map = {1,2,3,4,5,6,7,8}
gpio_map_rf = {1,2,3,4}
gpio_map_wifi = {5,6,7,8}

-- 初始化GPIO接口的模式为OUTPUT,即将信号输出到信号模块
for idx=1, #gpio_map do
    pin = gpio_map[idx]
    gpio.mode(pin, gpio.OUTPUT)
end

-- 初始化最后一次接收控制指令的时间,区分指令是否是新指令
last_wifi_time = 0
last_rf_time = 0
-- 访问地址,这里是我家用设备的访问地址,我设置了一个静态网址
url = 'http://服务器地址/smartlife/static/light.html'

-- 轮询,每1秒访问一次网页获取最新的信息
-- 这里使用tmr.alarm,而不是设置while...tmr.delay,是因为while...tmr.delay会卡住所有的进程,tmr.alarm类似于建立一个线程,不会卡住其他进程。 
tmr.alarm(0, 1000, 1, function ()
    -- 通过HTTP GET的方式获取网页数据
    http.get(url, nil, function(code, data)
        if (code < 0) then
          print("HTTP request failed")
        else
            -- 数据格式为:{"rf": ["0100", 1513249981], "wifi": ["0000", 1513278152]}
            -- sjson是固件中的sjson模块
            dt = sjson.decode(data)
            for k, v in pairs(dt) do 
                -- k是'rf', v是["0100", 1513249981]
                if k == "rf" then
                    part_data = v[1]
                    -- 将字符格式的时间戳转成整数
                    part_time = tonumber(v[2])

                    --获取的时间和最后一次的时间做对比
                    if last_rf_time == 0 then
                        last_rf_time = part_time
                    else
                        if part_time > last_rf_time then
                            for mode_idx=1, #part_data do
                                mode = part_data[mode_idx]
                                for idx=1,4 do
                                    v = string.sub(mode, idx,idx)
                                    gpio.write(gpio_map_rf[idx], tonumber(v))
                                end
                                tmr.delay(300000)
                                for idx=1,4 do
                                    gpio.write(gpio_map_rf[idx], 0)
                                end
                                tmr.delay(300000)
                            end
                            last_rf_time = part_time
                        end
                    end
                elseif k == "wifi" then
                    part_data = v[1]
                    part_time = tonumber(v[2])
                    if last_wifi_time == 0 then
                        last_wifi_time = part_time
                    else
                        if part_time > last_wifi_time then
                            for idx=1,4 do
                                v = string.sub(part_data, idx,idx)
                                gpio.write(gpio_map_wifi[idx], tonumber(v))
                            end
                            last_wifi_time = pa

Related Skills

View on GitHub
GitHub Stars6
CategoryDevelopment
Updated2y ago
Forks1

Languages

Python

Security Score

70/100

Audited on Jan 22, 2024

No findings