Smartlife
a small smart light project
Install / Use
/learn @tagword/SmartlifeREADME
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、射频、ZigBee、Z-Wave为主,它们各有优劣,可从功耗、效率、安全、集控、分控等多方面对比,这里主要说说Wi-Fi和射频两个方案。
-
Wi-Fi控制,是给开关添加Wi-Fi开关模块,让开关联网从云端获取指令,操作继电器控制开关的方案。任意一个设备都是单独产品。目前能看到大量的智能产品,里面都有类似的模块。
-
射频,是通过无线射频传递指令,通过给开关添加射频接收模块,接收来自发射模块的指令,来控制开关的方案,类似车子遥控。射频信号以433MHz和315MHz为主,产品成熟,接收端一般是学习型模块,发射端也采用通用的芯片模块。若要实现手机控制,需添加一个主机。
单火线电路和零火线电路
产品按电路可分零火线开关和单火线开关两种,前者稳定,后者目前还存在技术问题。智能模块本身也是用电器,需要保持运行来获取控制信号,目前单火线开关的技术是维持小电流让模块运行,却又达不到让电灯发光的功率,但如果电灯功率低于理论值3W,就会出现灯光闪烁的问题。目前了解到市面上已有单火电源模块:PI-3v3-B4。
云服务
我国网络运营商都不提供独立IP地址,IOT设备从云服务器获取控制指令是当前的主流方式。云服务的载体,可以是任意一台云服务器,也可以选择IOT的云服务平台。我是在自有服务器上搭建云服务。云服务的技术,采用RESTful和一个静态网页。操作流程为手机应用发送控制指令到云服务,云服务后台处理并更新静态网页,智能模块以轮询的方式获取控制指令,并控制电灯开关。
手机应用
IOS下的应用,使用Xcode,做个能实现HTTP POST的简单程序就好。
最终系统方案
因为单火线的限制和单火线开关闪烁的问题,最终方案是Wi-Fi和射频联合使用的方案,并且实现手机控制。把目标分解一下就成了采购相关物料,来完成下面几个任务:
- 制作“可联网射频发射主机”
- 制作”Wi-Fi开关“
- 制作“射频开关“
- 建立“云服务”
- 制作手机应用
物料
NodeMCU开发版
网上查了查资料是性价很高的板子,购买价格不到20元,基于ESP8266-12E Wi-Fi芯片。有详尽的资料NodeMCU Documentation,友好的自定义固件生成服务NodeMCU-Build,固件烧入程序NodeMCU PyFlasher,开发工具ESPlorer, 支持编程语言eLua和MicroPython,板上的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,这个模块还有一个优点是有三种模式:
- 开/关,关闭状态下按一下开,打开状态下再按一下关。
- 总开,开关总是打开,如果原来是打开状态,保持状态不变
- 总关,开关总是关闭,如果原来是关闭状态,保持状态不变
可以实现灯光开关单控制,也可是实现模式控制,如“观影模式”、“会客模式”。另外,每个模式可以接收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
node-connect
344.4kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
99.2kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
344.4kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
344.4kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
